I am about to start learning Rust after programming in C++. I am unsure how to create a function (or anything else generic) that takes a generic type as its template argument.
I have tried to compile the following code:
trait Monad {
fn singleton<T>(t: T) -> Self<T>;
fn compact<T>(mmt: Self<Self<T>>) -> Self<T>;
}
fn id<F>(fi: F<i8>) -> F<i8> {return fi;}
however this spits out a bunch of type argument not allowed errors.
In C++20 I would write:
template<typename<typename> M>
concept monad = requires(...){...};
template<typename<typename> F>
F<std::byte> id(F<std::byte> fi) {return fi;}
How would I implement this functionality in Rust?
Template template parameters are a limited form of higher-kinded types.
Rust also has a limited form of higher-kinded types in the form of "generic associated types". These are available on the nightly. Concretely, the
Monadexample might look like:playground
In a trait,
Selfis always a concrete type and not a type constructor. Because only concrete types satisfy traits. That's whySelf<T>is not accepted.To compare and contrast with C++, in Rust you cannot refer to
Vecwithout its type parameter as a type constructor.std::vectoritself is nameable in C++. Therefore, we have to use a stand-inVectorfor it in Rust. Also, our Rust implementation uses an unstable language feature.On the other hand, you'd struggle to finish writing that
monadconcept in C++. A template template parameter cannot be applied to any type. The type constructor is partial, and that partiality is implicit. A reasonable requirement for monad might be that it can be instantiated with anystd::movabletype and that the functions are defined forstd::movabletype parameters. That is not expressible in C++.So, in C++, you might write something like this:
In C++, you can't say a constraint holds for instantiations at all types of a certain shape (e.g.
std::movable). So you create a type that is of that shape and nothing more, and require that the constraint hold at just that one instantiation. People have taken to calling these types "archetypes". You hope that there are no specializations, overloads, or other things that might stop this satisfaction from generalizing to all types of that shape.Then, you have a choice about where the functions live. Template template parameters cannot have member functions, so you either put them in a particular instantiation (e.g.
M<T>is constructible fromT) or as a free function. Using member functions has downsides, because it cannot be externally implemented. With a free function, you need a way to identify the monad, so you end up wrapping up the template template parameter in a stand-in type as well.These are interesting to compare.