struct Foo<T>(T);
And you create Foo(42i32) and Foo(0.0f64), the compiler will create the equivalent to struct Fooi32(i32);
struct Foof64(f64);
In other languages like Java, generics are implemented the way that Rust does "trait objects" (&dyn Trait).Rust is not the only language that does this, to be clear.
If you're interested in a quick intro on the compiler side of this, you can read https://rustc-dev-guide.rust-lang.org/backend/monomorph.html
And now the thing is: with transparent signature ascriptions, functors are monomorphised in SML, instead of everything being hidden behind signatures (as is in the case of Rust with traits when you use dyn), which has semantic consequences. E.g. a struct returned by a functor may contain a type. You can't perform proper type-checking without monomorphising, because you don't know what the exact type is. E.g. in the following program, the final line couldn't be type-checked without monomorphisation:
signature ITERABLE = sig
type ElemT
type SrcT
val new_iter: SrcT -> unit -> ElemT option
end
signature LIST_ELEM_TYPE = sig
type T
end
functor ListIterFun (ListElemType: LIST_ELEM_TYPE): ITERABLE = struct
type ElemT = ListElemType.T
type SrcT = ElemT list
fun new_iter l = let val lr = ref l
in
fn () => case !lr of
nil => NONE
| (x::xs) => (lr := xs; SOME x)
end
end
structure IntElemType: LIST_ELEM_TYPE = struct
type T = int
end
structure IntListIter = ListIterFun(IntElemType)
val next = IntListIter.new_iter [1, 2, 3, 4, 5]
If I change the signature ascription on ListIterFun to an opaque ascription (:> ITERABLE), the final line won't type-check, because it's not obvious from the signature, that ElemT is int. So transparent signature ascriptions require monomorphisation (Rust traits without dyn), and opaque signature ascriptions free the compiler from having to do monomorphisation (Rust traits with dyn*).There was a lot of discussion of this issue when Go was settling on a design for its generics, under the phrase "reified generics".
Given
fn foo<T>(a: T, b: T) -> T { a + b }
The compiler will complain that you should have been explicit on how T is going to be used: error[E0369]: cannot add `T` to `T`
--> src/lib.rs:1:32
|
1 | fn foo<T>(a: T, b: T) -> T { a + b }
| - ^ - T
| |
| T
|
help: consider restricting type parameter `T`
|
1 | fn foo<T: Add<Output = T>>(a: T, b: T) -> T { a + b }
| +++++++++++++++++
whereas in C++ this would have been accepted until you called foo with two things that couldn't be added together, like a Rust macro[1].[1]: https://play.rust-lang.org/?version=nightly&mode=debug&editi...
pub fn foo<T: AsRef<X>>(x: T) {
inner_foo(x.as_ref());
}
fn inner_foo(_: &X) { todo!() }
can be instead done by the compiler automatically (turning monomorphized code back into polymorphic code, hence the polimorphization hame).[1]: https://rustc-dev-guide.rust-lang.org/backend/monomorph.html...
package main
import (
"fmt"
)
type wrapper[T any] struct {
Value T
}
func (w wrapper[T]) String() string {
return fmt.Sprintf("{%v}", w.Value)
}
func stringWrapped[T any](n int, v T) string {
if n == 0 {
return fmt.Sprintf("%v", v)
}
return stringWrapped(n-1, wrapper[T]{Value: v})
}
func main() {
n := 0
fmt.Scanf("%d", &n)
result := stringWrapped(n, "test")
fmt.Println(result)
}
Go refuses to compile because it can't possibly generate all instances of wrapper[T] that this program may use: wrapper[string], wrapper[wrapper[string]], wrapper[wrapper[wrapper[string]]], etc.[1]: https://play.rust-lang.org/?version=stable&mode=debug&editio...
[2]: https://play.rust-lang.org/?version=stable&mode=debug&editio...
^ This blows the stack because it keeps calling itself with no break condition, but shows how the type system accepted the code.
In C++ you can monomorphize as long as you can somehow prove the recursion terminates at compile time (for example by threading a static recursion counter).
It's probably exactly how templates work, except the details are invisible to users.
https://hacks.mozilla.org/2020/11/warp-improved-js-performan...
Compile time reflection AFAIK is available in D and Zig, and is planned for C++.
In C++ with SFINAE you can effectively do CTTI-style programming in C++. C++ has long had runtime type reflection as well (RTTI), though it needs to be compiled in. Looks like there's a boost library for CTTI.
I would however welcome static reflection with open arms. In Rust in particular, I’d prefer it if derive was implemented using static reflection, rather than proc macros.
Rust has no run-time type information except limited downcasts via `dyn Any` or explicitly derived traits on per-type basis, and these features compile to type-specific monomorphic code rather than calling some run-time reflection.
Ruts does not support inheritance either. But I have never missed either feature in a Rust program.