I have a struct MyStruct
that takes a generic parameter T: SomeTrait
, and I want to implement a new
method for MyStruct
. This works:
/// Constraint for the type parameter `T` in MyStruct
pub trait SomeTrait: Clone {}
/// The struct that I want to construct with `new`
pub struct MyStruct<T: SomeTrait> {
value: T,
}
fn new<T: SomeTrait>(t: T) -> MyStruct<T> {
MyStruct { value: t }
}
fn main() {}
I wanted to put the new
function inside an impl
block like this:
impl MyStruct {
fn new<T: SomeTrait>(t: T) -> MyStruct<T> {
MyStruct { value: t }
}
}
But that fails to compile with:
error[E0107]: wrong number of type arguments: expected 1, found 0
--> src/main.rs:9:6
|
9 | impl MyStruct {
| ^^^^^^^^ expected 1 type argument
If I try to put it like this:
impl MyStruct<T> {
fn new(t: T) -> MyStruct<T> {
MyStruct { value: t }
}
}
The error changes to:
error[E0412]: cannot find type `T` in this scope
--> src/main.rs:9:15
|
9 | impl MyStruct<T> {
| ^ not found in this scope
How do I provide an implementation of a generic struct? Where do I put the generic parameters and their constraints?
In Rust, generics refer to the parameterization of data types and traits. Generics allows to write more concise and clean code by reducing code duplication and providing type-safety. The concept of Generics can be applied to methods, functions, structures, enumerations, collections and traits.
Short for “type,” T is the default choice of most Rust programmers. When we use a parameter in the body of the function, we have to declare the parameter name in the signature so the compiler knows what that name means.
The impl keyword is primarily used to define implementations on types. Inherent implementations are standalone, while trait implementations are used to implement traits for types, or other traits. Functions and consts can both be defined in an implementation.
GATs (generic associated types) were originally proposed in RFC 1598. As said before, they allow you to define type, lifetime, or const generics on associated types. If you're familiar with languages that have "higher-kinded types", then you could call GATs type constructors on traits.
The type parameter <T: SomeTrait>
should come right after the impl
keyword:
impl<T: SomeTrait> MyStruct<T> {
fn new(t: T) -> Self {
MyStruct { value: t }
}
}
If the list of types and constraints in impl<...>
becomes too long, you can use the where
-syntax and list the constraints separately:
impl<T> MyStruct<T>
where
T: SomeTrait,
{
fn new(t: T) -> Self {
MyStruct { value: t }
}
}
Note the usage of Self
, which is a shortcut for MyStruct<T>
available inside of the impl
block.
Remarks
The reason why impl<T>
is required is explained in this answer. Essentially, it boils down to the fact that both impl<T> MyStruct<T>
and impl MyStruct<T>
are valid, but mean different things.
When you move new
into the impl
block, you should remove the superfluous type parameters, otherwise the interface of your struct will become unusable, as the following example shows:
// trait SomeTrait and struct MyStruct as above
// [...]
impl<T> MyStruct<T>
where
T: SomeTrait,
{
fn new<S: SomeTrait>(t: S) -> MyStruct<S> {
MyStruct { value: t }
}
}
impl SomeTrait for u64 {}
impl SomeTrait for u128 {}
fn main() {
// just a demo of problematic code, don't do this!
let a: MyStruct<u128> = MyStruct::<u64>::new::<u128>(1234);
// ^
// |
// This is an irrelevant type
// that cannot be inferred. Not only will the compiler
// force you to provide an irrelevant type, it will also
// not prevent you from passing incoherent junk as type
// argument, as this example demonstrates. This happens
// because `S` and `T` are completely unrelated.
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With