diff --git a/text/3892-complex-numbers.md b/text/3892-complex-numbers.md new file mode 100644 index 00000000000..2cb85c26184 --- /dev/null +++ b/text/3892-complex-numbers.md @@ -0,0 +1,227 @@ +- Feature Name: complex-numbers +- Start Date: 2025-12-02 +- RFC PR: [rust-lang/rfcs#3892](https://github.com/rust-lang/rfcs/pull/3892) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +## Summary +[summary]: #summary + +FFI-compatible and calling-convention-compatible complex types are to be introduced into `core` to ensure synchronity with C primitives. + +## Motivation +[motivation]: #motivation + +The definition of complex numbers in the C99 standard defines the _memory layout_ of a complex number but not its _calling convention_. +This makes crates like `num-complex` untenable for calling C FFI functions containing complex numbers without at least a level of indirection (`*const Complex`) or the like. +Only in `std` is it possible to make an additional repr to match the calling convention that C uses across FFI boundaries. +In essence, this RFC makes code like this: +```C +extern double _Complex computes_function(double _Complex x); +``` +callable in Rust without indirection: +```rust +extern "C" { + fn computes_function(x: Complex) -> Complex; +} +fn main() { + let returned_value = computes_function(Complex::new(3, 4)) +} +``` +using the standard library's FFI-compatible complex numbers. + +## Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +`Complex` numbers can be instantiated with any component type using `Complex::new(re, im)` where `re` and `im` are of the same type ( includes all numbers). +```rust +let x = Complex::new(3.0, 4.0); +``` +They can even be passed as an array: +```rust +let y = Complex::from([3.0, 4.0]); +``` +or as a tuple: +```rust +let z = Complex::from((3.0, 4.0)); +``` +They can even be passed in polar form (but only as a float): +```rust +let polar = Complex::from_polar(3.0, f32::PI/2.0); +``` +They are added and multiplied as complexes are: +```rust +let first = Complex::new(1.0, 2.0); +let second = Complex::new(3.0, 4.0); +let added = first + second; // 4 + 6i +let multiplied = first * second; // -4 + 10i +``` + +They can be divided using normal floating-point division: +```rust +let float_first = Complex::new(1.0, 2.0); +let float_second = Complex::new(3.0, 4.0); +let divided = float_second / float_first; // 2.4 - 0.2i +``` + +You can even calculate the complex sine, cosine and more: +```rust +let val = Complex::new(3.0, 4.0); +let sine_cmplx = csin(val); // 3.8537380379 - 27.016813258i +``` +If you want to call certain C libraries with complex numbers, you use this type: +```C +// in the C library +extern double _Complex computes_function(double _Complex x); +``` +```rust +// in YOUR Rust code +extern "C" { + fn computes_function(x: Complex) -> Complex; +} +fn main() { + let returned_value = computes_function(Complex::::new(3.0, 4.0)) +} +``` + +## Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +The `core` crate will provide implementations for operator traits for possible component types. For now, complex operations like `Mul` and `Div` are defined specifically for each floating-point type, while `Add` and `Sub` are defined for any components that themselves implement `Add` and `Sub`. +Calls to some `libgcc` functions may also be needed, and will be emitted by the backend via compiler-builtins, specifically `__mulsc3`, `__muldc3`, `__divsc3` and `__divdc3` for the proper and complete implementation of these types. +They will have an internal representation similar to this (with public fields for real and imaginary parts): +```rust +// in core::complex +#[lang = "complex"] +#[repr(C)] +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct Complex {pub re: T, pub im: T}; +``` +have construction methods and `From` impls: +```rust +impl Complex { + fn new(re: T, im: T) -> Self; +} + +impl From<(T, T)> for Complex { + fn from(value: (T, T)) -> Self; +} +impl From<[T; 2]> for Complex { + fn from(value: [T; 2]) -> Self; +} +``` +and have arithmetic implementations similar to this: +```rust +impl Add for Complex { // and for corresponding real types + type Output = Self; + + fn add(self, other: Self) -> Self::Output; +} + +impl Sub for Complex { // and for corresponding real types + type Output = Self; + + fn sub(self, other: Self) -> Self::Output; +} +``` +and then implementations of those functions which require external calls to C (`__mulsc3` etc.), which will be in `std` and not `core`: +```rust +impl Mul for Complex { // calls to __mulsc3 will be required here for implementation details and corresponding real types will also be implemented + type Output = Self; + + fn mul(self, other: Self) -> Self::Output; +} +impl Mul for Complex { // calls to __muldc3 will be required here for implementation details and corresponding real types will also be implemented + type Output = Self; + + fn mul(self, other: Self) -> Self::Output; +} +impl Div for Complex { // calls to __divsc3 will be required here for implementation details and corresponding real types will also be implemented + type Output = Self; + + fn div(self, other: Self) -> Self::Output; +} +impl Div for Complex { // calls to __divdc3 will be required here for implementation details and corresponding real types will also be implemented + type Output = Self; + + fn div(self, other: Self) -> Self::Output; +} +``` +The floating point numbers shall have sine and cosine and tangent functions, their inverses, their hyperbolic variants, and their inverses defined as per the C standard and with Infinity and Nan values defined as per the C standard. +## Drawbacks +[drawbacks]: #drawbacks + +The implementation surface of the complex types means more items for the libs and lang teams to maintain. +Also, the multiple emitted calls to `libgcc.so` (`__mulsc3` and the like) may cause a bit of overhead and may not be what the Rust lang team and compiler team want. + +## Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +The rationale for this type is mostly FFI: C libraries that may be linked from Rust code currently cannot provide functions with direct struct implementations of Complex - they must be hidden under at least a layer of indirection. This is because of the undefined calling convention of complex numbers in C. For example: on powerpc64-linux-gnu, [returning double _Complex doesn't do the same thing as returning a struct with a field of type double[2].](https://gcc.godbolt.org/z/hh7zYcnK6) However, it is not always possible to write a C complex-valued function that wraps the first function in a pointer. Thus, FFI becomes a problem if such complex-valued functions are passed by value and not by reference. +Additionally, another issue this solves is to have a unified API for complex numbers. Right now, many crates are using their own implementation (`num-complex` could serve as a unifying factor, but other crates do not implement the same complex numbers, such as `nalgebra`, due to less care over incompatibility concerns), which makes it difficult to implement unifying interfaces without complicating the code with too many conversion functions. This serves a problem for crates that use different implementations of complex numbers for different tasks (`sprs` relying on `num-complex` and `nalgebra` implementing its own variation) + +You could theoretically do something like this: +```c +double _Complex function(double _Complex value); +void wrapper_function(double _Complex* value, double _Complex* out) { + *out = function(*value); +} +``` +for all functions you wish for. But this still needs to happen in C. + +### Alternatives: +- Don't do this: There are, obviously, millions of alternatives on crates.io, the foremost being `num-complex`. However, I believe that if we wish to support proper FFI with C, then a standard type that matches calling conventions with C complex numbers is an important feature of the language. Hence, I do not recommend this idea. +- Use a polar layout: Polar complex numbers are undoubtedly a more optimal solution for multiplying complexes. However, I believe that if we wish to have proper FFI with C, then complex number layout should be chosen in accordance with the layout that is used in the C standard, and that is the orthogonal layout. This is also the layout used by most other languages and other crates on crates.io. +- Non-generic primitive types: These are, obviously, the most obvious and practical solution. However, if we implemented lots of such types, then we would not be able to expand for `f16` and `f128` support without repeating the code already implemented. It would be extremely repetitive and tedious to document new types and their behavior, even if we used macros to generate implementations + +## Prior art +[prior-art]: #prior-art + +FORTRAN, C, C++, Go, Perl and Python all have complex types implemented in the standard library or as a primitive. This clearly appears to be an important feature many languages have. +For example, in Python: +```py +complex_num = 1 + 2j +complex_second = 3 + 4j +print(complex_num * complex_second) +``` +or in C: +```c +float _Complex cmplx = 1 + 2*I; +float _Complex two_cmplx = 3 + 4*I; +printf("%.1f%+.1fi\n", creal(cmplx * two_cmplx), cimag(cmplx * two_cmplx)); +``` +Even in Rust, it has been discussed two times in IRLO: +- [First discussion](https://internals.rust-lang.org/t/c-compatible-complex-types-using-traits/13757) +- [Second discussion](https://internals.rust-lang.org/t/standard-complex-number-in-std-library/23748) + +Many crates, like `num-complex` also provide this feature, though it is not FFI-safe. +## Unresolved questions +[unresolved-questions]: #unresolved-questions + +Should this type be in `core::ffi`? This type's purpose is mostly FFI, but it might be useful in library contexts as well, so I am not sure if we should place it in `core::ffi`. + +## Future possibilities +[future-possibilities]: #future-possibilities + +- Maybe later on, we can think of adding a special custom suffix for complex numbers (`1+2j` for example), and using that as a simpler way of writing complex numbers if this RFC is accepted? This is very similar to how most languages implement complex numbers? Or perhaps we could consider a constant: +```rust +impl Complex { + const I: T = Complex::new(T::zero(), T::one()); +} +``` +where `zero` and `one` is implemented on the `Float` trait similar to `num_traits`? +Or maybe we could have a method on normal numbers: +```rust +// for example +impl f32 { + fn i(self) -> Complex { + Complex::new(0, self) + } +} +``` +that could help simplify the life of people who otherwise would have to keep writing `Complex::new()`? +- Should we support Imaginary eventually? This RFC doesn't cover it, but I think we can do this later in another RFC. +- Eventually we may support Gaussian integers (an extension of the real integers) which have a Euclidean division procedure with remainder. GCC has these, and we could theoretically eventually support these integers alongside GCC FFI. +- We can also support f16 and f128 once methods for them are stabilised. +- We should also think about a `Display` implementation. Should we support something like `1 + 2i` or something else? Should we not make a `Display` impl at all, and just use re() and im() for the implementation? +- We should also consider adding aliases (like c32 and c64) for floating points once they are established, to allow for a shorthand syntax. +- Eventually, we should also consider adding polar conversions (e.g, `modulus` and `angle`)