Understanding Generics


#1

Hi,

I’m new to rust, and currently learning about generics. I have run into an issue that I don’t fully understand, and wanted to seek some clarification.

In rust (I’m using 1.4), you can write the following function, and it compiles as expected:

struct Foo; fn test() -> Foo { Foo }

If we make this function generic, it generates an E0425 error, complaining that T is an unresolved name inside the body of the function.

fn test<T>() -> T { T }

I’m pretty sure this comes down to a lack of understanding on my part - I guess what I’d like to understand is for a generic function, in what circumstances is a generic type not available to the body of the function?

Thanks,

James.


#2

Well, first of all, that syntax only works for types that don’t contain any data.

The more general issue is that when you’re dealing with generic types in Rust, you can only do things the compiler knows can be done on absolutely any type in that position.

Let me use a different example:

fn add<T>(a: T, b: T) -> T { a + b }

This won’t compile. All the compiler knows about T is that it’s a type [1]. It doesn’t have any reason to believe that those Ts, whatever they are, can be added. If you want to add them, you need to tell the compiler:

fn add<T: Add<Output=T>>(a: T, b: T) -> T { a + b }

This has two effects: first, we can only instantiate the generic with types T that implement Add<Output=T>. So we can’t use add((), ()), because () doesn’t implement Add<Output=()>. The second thing is that within the function, we are now allowed to make use of addition because the compiler can prove that whatever type T happens to be, it must support it. This is also why these are sometimes called “constraints”: you’re constraining the set of types you can instantiate the generic with.

So, to come back to the original question: not every type T can be constructed using that unitary form. So, what constraint do you have to place on T to restrict it to types that support unitary construction?

… uh, you don’t. There isn’t one.

Sorry.

Closest thing would be types that implement Default:

fn test<T: Default>() -> T { Default::default() }

Of course, at that point, you might as well just use Default::default() instead of test().


[1]: Actually, it also knows the type has a fixed stack size, but that’s for slightly complicated reasons I don’t want to go into because I’m lazy.


#3

I assume you expect constructing default-initialized value of type T and returning it from function. In Rust, it’s not possible. In fact, there’s no default value for any type built into language. You’re required to properly initialize value before doing anything useful with it.
What you possibly seek is Default trait, which provides such capability. Try #[derive(Default)] on a concrete struct.


#4

To try to clarify @DanielKeep’s response because I had to think about his response for a bit before I could think of a type which couldn’t be constructed like this (though I think his answer is quite good):

struct Foo;
fn test1() -> Foo { Foo } // works
fn test2() -> i32 { i32 } // doesn't work

struct Bar(i32);
fn test3() -> Bar { Bar } // doesn't work
fn test4() -> Bar { Bar(3) } // works
// doesn't work for `i32` and `Bar` so it doesn't work for all types
fn test5<T>() -> T { T }

After the fact, this seems like it should be quite obvious though.