Extend the extern "C" calling convention: Whenever an impl ComplexSingle parameter is encountered, Rust will use ComplexSingle::into_array() to convert the Complex Number into a [f32; 2] and pass it ABI compatible to the _Complex float C data type (The [0] entry specifies real, the [1] entry the imaginary part). When impl ComplexSingle is used as return value, the return value is interpreted as a [f32; 2] that is then passed to ComplexSingle::from_array() before being returned to user code. In a similar fashion impl ComplexDouble is used for parameters and return values ABI compatible to the _Complex double C data type.
Why not just use a struct? I personally dislike the idea of Rust implicitly generating additional function calls. Explicit is better than implicit, IMO. When I call csin(complex) it should be exactly that.
Personally I don't think this is any simpler than just introducing a new ComplexF32/ComplexF64 type.
Alternatively, the C standard requires complex types to have "the same representation and alignment requirements as an array type containing exactly two elements of the corresponding real type." Since arrays aren't first-class types in C, it means Rust should have some flexibility in how it interprets the ABI of [f32; 2]/[f64; 2]. Rust could just declare these two types as having the same ABI as float _Complex/double _Complex.
Given that you concur that this is already the case, why does the standard library need to be involved, let alone core? It sounds much more like a reason to define simple transparent wrapers for them in libc.
But that could be changed since [f32; 2] and [f64; 2] currently aren't FFI-safe. They could be declared FFI-safe with _Complex's ABI.
(f64, f64) isn't FFI-safe either unless you make it #[repr(C)], but then it has the ABI of a struct, which differs from the ABI of _Complex on some platforms. I suppose you could declare the #[repr(Rust)] representation FFI-safe and give it _Complex's ABI, but I personally prefer making the array type FFI-safe and _Complex-compatible.
Pointers to them are, and may not necessarily be compatible.
My $0.02 is that rust should either define a standard library type, or a custom repr (perhaps the latter be used to (possibly unstably) implement the former). In any case, it should make room for extensions, like _Complex int, though I agree that _Complex float and _Complex double are much more useful. On a semi-related note, I do have the opinion that rust should provide some method to access the platform long double type.
How come, since [f64; 2] should quite literally have the same object representation as an array of two elements of the base type as required from the complex type? The array should be C-compatible while tuples types have no layout guarantee from Rust's point of view. What am I overlooking?
But we're not talking about pointers. Rust's [f32; 2] already has the exact same size and layout of C's float _Complex. Changing the ABI of [f32; 2] will have no impact on pointers.
Having equivalent representation and alignment does not imply they are compatible types (the converse of that is true, compatible types have equivalent representation, size, and alignment requirements). I don't believe _Complex T and T[2] are required to be compatible, nor the same of pointers thereof.
They are required to be compatible. Previously I quoted the part of the C standard that says so. Here's the paragraph:
Each complex type has the same representation and alignment requirements as an array type containing exactly two elements of the corresponding real type; the first element is equal to the real part, and the second element to the imaginary part, of the complex number.
When an array type is used in a function parameter list, it is transformed to the corresponding pointer type: int f(int a[2]) and int f(int* a) declare the same function.
If the two things need to be disjoint, it should be something that's already disjoint in the language -- probably a type. (Maybe could be a repr(complex), but that seems overly complicated, since it's unclear that multiple types having this ABI would be valuable.)
This is true for the in memory representation, but it doesn't say anything about the ABI representation. However I agree. We could decide that [f32; 2] and [f64; 2] are passed just like _Complex float and _Complex double in all extern "C" functions. Meaning that the Rust signature of csin would be
extern "C" {
csin(a: [f64;2]) -> [f64;2];
}
and libaries need to wrap these types (just like c string and raw pointers). Instead of [f64;2] one could also choose (f64, f64), I guess. Maybe that would be even better
One advantage of a repr would be the ability to interact with compiler-specific extensions, as I mentioned. I believe the Sys-V ABI does specify the abi of _Complex <int type>, so being able to interact with such types may be beneficial.
A big dissadvantage with a repr(complex), in my opinion is covering the followring case:
#[rep(complex)]
pub struct Complex<T> {
re: T,
im: T
}
How do you pass, e.g. Complex<char> to a function? Is this case undefined? Is it defined to behave like rep(C). What happens if C decides to add a _Complex int type?
Currently, the abi for char is not defined, I believe. However, if it was defined as compatible with char32_t, then it would depend. If _Complex char32_t is valid under the applicable C abi, then that type shall have the same abi as _Complex char32_t, otherwise, the abi is implementation-defined.
Given a struct F, that contains a member of type [T;2] and any number of other members with size 0 and alignment 1, declared repr(complex) has the same layout as [T;2]. Given T is a type which has corresponding abi with some type defined in C or by the C standard T', if the C standard or the platform C abi defines the type _Complex T', F has corresponding abi with _Complex T'. Otherwise, the abi of F is implementation-defined.
The same specification could be used with a standard library type.