Why need to specify generic type when the type is optional




#[derive(Debug)]
struct Test<T = String>{
    foo:Option<T>
}


fn main() {
    let f = Test{
        foo:None
    };
    
    println!("{:?}",f.foo);
}

playground
since I've give the default T is String, why need to specify the type when use Test struct?

Inference does not consider type defaults (so far), basically.

You can

let f: Test = Test { foo: None }
let f = Test { foo: None::<String> }

#[derive(Debug, Default)]
//              ^^^^^^^
struct Test<T = String>{
    foo:Option<T>
}

// ...
let f = <Test>::default();

Or various other workarounds.

2 Likes

thanks for your note, it seems also verbose, I'll just pass whatever type to work around

Why does this last version work with the angle brakets but not without?

Default values for generic parameters have only one effect: they allow you to omit the generic parameters when writing down the type. You don't write the Test type anywhere, so the default is not used anywhere.

As a rule of thumb, if you have some code that would still compile without "you need to specify this generic parameter" errors when you remove the default, then adding a default has no effect on that code.

4 Likes

add type annotations after variable can omit the generic parameters,

let f:Test = Test{
foo:None
}

I thought it could infer by compiler and I can omit generic parameters at the right part

The Github comment I linked is an explanation about that in particular.

1 Like

Ah yes. It's quite a well-written answer. I'll copy it here for reference:

QuineDot commented on Dec 25, 2022

I did believe that T:: and <T>:: were equivalent, but it's not the case.

What's the difference between T:: and <T>::? Can somebody explain to me why this hack actually works?

Types in part of a (path) expression, like T::, use inference for all elided type parameters -- including defaulted ones -- and do not fallback to the defaulted type when running into ambiguity. That's pretty much what this RFC was about. So these are equivalent and cannot currently fallback to the defaulted type (and thus fail to compile):

    let _ = Foo::Baz;
    let _ = Foo::<_>::Baz;

Where as in type ascription location, you can't elide non-defaulted parameters, but if you elide defaulted parameters they take on the default value. (Then the inferred type parameter in the expression can be resolved from the ascription.) So these are all the same and compile:

    let _: Foo = Foo::Baz;
    let _: Foo<String> = Foo::Baz;
    let _: Foo = Foo::<_>::Baz;
    let _: Foo<String> = Foo::<_>::Baz;

And then we have <T>:: in an expression. The T in <T> is always a type -- on its own inside the <>, T is not part of an expression. Similar to type ascription, you can't elide non-defaulted parameters, but if you elide default parameters they take on the default value. So these are the same:

    let _ = <Foo>::Baz;
    let _ = <Foo<String>>::Baz;

<T> was introduced in RFC 0132, which also says that Foo::<_>::... is now equivalent to <Foo<_>>::.... And sure enough, these don't compile either, as the inference still does not fallback to defaults.

    let _ = <Foo<_>>::Baz;
    let _: Foo<_> = Foo::Baz;

Summary

  • In an expression position (where you need turbofish)
    • You can elide parameters whether they are defaulted or not
    • Elided parameters, with or without a default, will become fresh inference variables (_)
      • Which do not fallback to parameter defaults
  • In a type position (where you don't need turbofish)
    • You cannot elide non-default parameters
    • Elided default parameters will use the default, not inference
    • "Type position" includes type ascription and <T>
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.