Strange behavior default generic parameter type

The first piece of code throws an error, all the others work. Why?


Error!

    #[derive(Debug, Default)]
    struct StructName<T=u32> { // default generic parameter type
        field: T,
    }

    fn main() {
        let a = StructName { ..Default::default() };
    //          ^^^^^^^^^^ Error: cannot infer type for type parameter `T` 
    //                            declared on the struct `StructName`
        
        println!("{:?}", a);
    }

Ok!

    #[derive(Debug, Default)]
    struct StructName<T=u32> {
        field: T,
    }

    fn main() {
        let a: StructName = StructName { ..Default::default() };
    //       ^ ^^^^^^^^^^ Ok!
        println!("{:?}", a);
    }

Ok!

    #[derive(Debug, Default)]
    struct StructName<T=u32> {
        field: T,
    }

    fn main() {
        let a = StructName { 
            ..Default::default() 
        } as StructName;
    //    ^^ ^^^^^^^^^^ Ok!      
        
        println!("{:?}", a);
    }

Ok!

    #[derive(Debug, Default)]
    struct StructName<T=u32> {
        field: T,
    }

    type Settings = StructName;
    //   ^^^^^^^^^^^^^^^^^^^^^ Ok! 

    fn main() {
        let a = Settings { ..Default::default() };
        println!("{:?}", a);
    }

1 Like

My best guess is that the defaults only apply to StructName being used as a type, not as a constructor. Maybe the situation is more clear with tuple-like structs, e.g.

struct StructName<T=u32>(T);

where StructName literally becomes a function with the type

fn StructName<T>(_: T) -> StructName<T> { … }

and functions don’t support default parameters either.


To give a reason why things might be this way: In the type StructName<T> you can never leave out the parameter unless it has a default value. If you need it inferred, you’ll need to write StructName<_> with an underscore. OTOH the constructor call only has parameters optionally (and with the turbofish) e.g. StructName::<u32> { ..Default::default() } would be the explicit way of passing the parameter. And even without the T=u32 default, you can write StructName without parameters for the constructor and it means the same as StructName::<_> i.e. the parameter is inferred.


As for the concrete examples you provide, the let a: Type = … or expr as Type syntax forms do expect types, so it makes sense the default parameter applies. The type Settings = StructName; is clearly the same as type Settings = StructName<u32>; as well, a slightly more magical thing is the fact that this type definition also introduces a new name / synonym for the constructor, that is Settings { … } as a shorthand for StructName::<u32> { … }.


Some testing later…… TIL that tuple-like structs don’t support using type synonyms for constructors while ordinary structs do. E.g. with

struct StructName<T = u32>(T);
type Synonym = StructName;

you can’t write Synonym(42) (but you can still write Synonym { 0: 42 }).

5 Likes

This doesn't work either.

#[derive(Debug, Default)]
   struct StructName<T=u32> {
       field: T,
   }

   fn main() {
       let a = StructName { ..StructName::default() };

       println!("{:?}", a);
   }
error[E0282]: type annotations needed for `StructName<T>`
 --> src/main.rs:7:17
  |
7 |         let a = StructName { ..StructName::default() };
  |             -   ^^^^^^^^^^ cannot infer type for type parameter `T` declared on the struct `StructName`
  |             |
  |             consider giving `a` the explicit type `StructName<T>`, where the type parameter `T` is specified

I tried that one too. Same situation: AFAICT the rule is that default parameters only apply if parameters couldn’t be elided (with the meaning that they’re supposed to be inferred) anyways, which seems to coincide with when parameters don’t need the :: “turbofish” separator. StructName::default does work with elided parameter even without the default and it also needs a turbofish, i.e. StructName::<T>::default for explicit arguments. To give another example: Writing <StructName>::default() on the other hand works, since with that syntax, you can’t elide anything without a default and you don’t need a turbofish either.


what I mean by

Writing <StructName>::default()

is the following

#[derive(Debug, Default)]
   struct StructName<T=u32> {
       field: T,
   }

   fn main() {
       let a = StructName { ..<StructName>::default() };

       println!("{:?}", a);
   }
2 Likes
<StructName>::default()

I wasn't aware that was a valid syntax and I don't understand it. Could you please explain it?

Also, I am having trouble understanding this sentence:

It's a qualified path, and usually has an as Trait like the examples in the link.

1 Like

This syntax is particularly useful with type that have some special non-path syntax like slices, references, trait objects, etc. E.g.:

let x: &[u8] = &[1,2,3];
let it = <[u8]>::iter(x);

This kind of syntax is, as @quinedot already mentioned, also used for fully qualified syntax for trait method calls, e.g. <u8 as Clone>::clone(&0) and there is the possibility to leave out either the trait or the type, i.e. <u8>::clone(&0) or <Clone>::clone(&0) which then can be written u8::clone(&0) or Clone::clone(&0), repectively, too.


Okay… what I was saying:

StructName::default does work with elided parameter even without the default

// `StructName` without the default `T=u32`
struct StructName<T> {
    field: T
}
fn main() {
    // `StructName::default` doesn’t have any `u32` parameter and
    // still works (because no parameter here means *inferred* parameter
    let a: StructName<u32> = StructName::default();
}

and it also needs a turbofish

struct StructName<T> {
    field: T
}
fn main() {
    // if I *want* to give a paremeter I need to write
    let a = StructName::<u32>::default();
    // i.e. `StructName<u32>::default();` doesn’t work
}
1 Like

Nevermind, judging by the compiler error messages, this will actually try to use a trait object, so it won’t work for Clone. So only Clone::clone(&0) is an option.

Some example where both the trait and the type has a parameter:

let x: Vec<u8> = …;

////////////////////////////////////////////////////

// method call
let _r: &[u8] = x.as_ref();

// fully qualified
let _r: &[u8] = <Vec<u8> as AsRef<[u8]>>::as_ref(&x);

////////////////////////////////////////////////////

// type missing
let _r: &[u8] = <_ as AsRef<[u8]>>::as_ref(&x);

// `<AsRef<[u8]>>::as_ref(&x)` would compile but would be equivalent
// to `<dyn AsRef<[u8]>>::as_ref(&x)` i.e. the same as
// `<dyn AsRef<[u8]> as AsRef<[u8]>>::as_ref(&x as &dyn AsRef<[u8]>)`

// trait (and `as`) missing
let _r: &[u8] = <Vec<u8>>::as_ref(&x);

// doesn’t work
// let _r: &[u8] = <Vec<u8> as _>::as_ref(&x);

////////////////////////////////////////////////////

// type missing, inferred trait parameter
let _r: &[u8] = <_ as AsRef<_>>::as_ref(&x);

// doesn’t work:
// let _r: &[u8] = <_ as AsRef>::as_ref(&x);

// trait missing, inferred type parameter
let _r: &[u8] = <Vec<_>>::as_ref(&x);

// doesn’t work:
// let _r: &[u8] = <Vec>::as_ref(&x);

////////////////////////////////////////////////////

// type missing, no enclosing <> or `as`, instead turbofish:
let _r: &[u8] = AsRef::<[u8]>::as_ref(&x);

// doesn’t work:
// let _r: &[u8] = AsRef<[u8]>::as_ref(&x);

// trait missing, no enclosing <> or `as`, instead turbofish:
let _r: &[u8] = Vec::<u8>::as_ref(&x);

// doesn’t work:
// let _r: &[u8] = Vec<u8>::as_ref(&x);

////////////////////////////////////////////////////

// type missing, no enclosing <> or `as`, inferred parameter
let _r: &[u8] = AsRef::<_>::as_ref(&x);
let _r: &[u8] = AsRef::<>::as_ref(&x);
let _r: &[u8] = AsRef::as_ref(&x);

// trait missing, no enclosing <> or `as`, inferred parameter
let _r: &[u8] = Vec::<_>::as_ref(&x);
let _r: &[u8] = Vec::<>::as_ref(&x);
let _r: &[u8] = Vec::as_ref(&x);

(playground)

2 Likes

Thanks!

That's a lot of possible variations. I didn't realize it was so flexible.

I had only ever seen the explicit form <type as trait>

I also just learned that ::<>:: is legal and equivalent to ::<_>::

I wouldn't have thought to try that.

In general, it’s more like it’s equivalent to not giving any parameters at all (so in these cases ::<>:: is like ::). It’s a bit of a weird corner case, I’m not actually sure why it is allowed like this but it is what it is. E.g. if there’s more than one parameters then ::<> might be equivalent to ::<_, _, > or even with lifetimes something like ::<'_, '_, _, _> etc, depending on the type/function used.

1 Like

I kind of figured it was like that. The example only had a single type parameter, but I get it.

Cool stuff.

Here’s some more variation:

let _r: &[u8] = <Vec::<u8> as AsRef::<[u8]>>::as_ref(&x);
let _r: &[u8] = <Vec<u8> as AsRef::<[u8]>>::as_ref(&x);
let _r: &[u8] = <Vec::<u8> as AsRef<[u8]>>::as_ref(&x);
let _r: &[u8] = <Vec::<u8>>::as_ref(&x);
let _r: &[u8] = <_ as AsRef::<[u8]>>::as_ref(&x);
let _r: &[u8] = <Vec::<_>>::as_ref(&x);
let _r: &[u8] = <_ as AsRef::<_>>::as_ref(&x);

Even in places where the :: part of the turbofish is not required it is still allowed.

I actually just read that in the reference. It is necessary to disambiguate in expressions, but not in types, although it is still allowed.

That's a dizzying amount of variety. :grinning:

Thank you steffahn!

here is another interesting example of an error:

#[derive(Debug, Default)]
struct StructName<A = u32, B = String> {
    field_a: A,
    field_b: B,
}

fn main() {
    let a = StructName {
        field_a: String::from("Spring"),
        ..<StructName>::default()
    };

    println!("{:?}", a);
}

Error:

error[E0308]: mismatched types
  --> src/main.rs:12:11
   |
12 |         ..<StructName>::default()
   |           ^^^^^^^^^^^^^^^^^^^^^^^ expected struct `String`, found `u32`
   |
   = note: expected struct `StructName<String, _>`
              found struct `StructName<u32, String>`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.

But this work:

#[derive(Debug, Default)]
struct StructName<A = u32, B = String> {
    field_a: A,
    field_b: B,
}

fn main() {
    let a = StructName {
        field_a: String::from("Spring"),
        field_b: 42,
        // Ok
    };

    println!("{:?}", a);
}

And this :slight_smile:

#[derive(Debug, Default)]
struct StructName<A = u32, B = String> {
    field_a: A,
    field_b: B,
}

fn main() {
    let a = StructName {
        field_a: String::from("Spring"),
        field_b: <StructName>::default()
    };

    println!("{:?}", a);
}

This is very interesting, here is an example:

#[derive(Debug, Default)]
struct StructName<A = u32, B = String> {
    field_a: A,
    field_b: B,
}

type Settings = StructName;

fn main() {
    let a = Settings {
        field_a: String::from("Spring"),
        field_b: 42,
    };

    println!("{:?}", a);
}

Error:

error[E0308]: mismatched types
  --> src/main.rs:11:18
   |
11 |         field_a: String::from("Spring"),
   |                  ^^^^^^^^^^^^^^^^^^^^^^ expected `u32`, found struct `String`

error[E0308]: mismatched types
  --> src/main.rs:12:18
   |
12 |         field_b: 42,
   |                  ^^
   |                  |
   |                  expected struct `String`, found integer
   |                  help: try using a conversion method: `42.to_string()`

But without synonym this will work:

#[derive(Debug, Default)]
struct StructName<A = u32, B = String> {
    field_a: A,
    field_b: B,
}

fn main() {
    let a = StructName {
        field_a: String::from("Spring"),
        field_b: 42,
    };

    println!("{:?}", a);
}

It seems to me that I have begun to better understand what is happening here.

These two examples work, but having to put the right amount of underscores ... I can't found an elegant way to write this.

Example A:

#[derive(Debug, Default)]
struct StructName<A=u32, B=bool, C=u8> {
   field_a: A,
   field_b: B,
   field_c: C,
}

fn main() {
   let a = StructName { 
       field_a: String::from("Spring"), 
       field_b: String::from("Summer"), 
       ..<StructName<_, _>>::default() 
   };

   println!("{:?}", a);
}

Example B:

#[derive(Debug, Default)]
struct StructName<A=u32, B=bool, C=u8> {
   field_a: A,
   field_b: B,
   field_c: C,
}

fn main() {
   let a = StructName { 
       field_a: String::from("Spring"), 
       ..<StructName<_>>::default() 
   };

   println!("{:?}", a);
}
#[derive(Debug, Default)]
struct StructName<A = u32, B = String> {
    field_a: A,
    field_b: B,
}

fn main() {
    let a = StructName::<_> {
        field_a: String::from("Spring"),
        ..<_>::default()
//      ^^^^^^^ emoticon of how I feel writing this ;-)
    };

    println!("{:?}", a);
}

After a couple recent related threads here, my general takeaway is that default type parameters are an incomplete feature.

3 Likes

Wow… <_>::default() — I would’ve never tried eliding the whole type and trait. But apparently, it works. Fascinating stuff…

Immediately tried if this also works for enum variants similar to this proposal, but it doesn’t.

let x: Option<()> = <_>::None;
error[E0599]: no associated item named `None` found for type `_` in the current scope
  --> src/main.rs:2:30
   |
 2 |     let x: Option<()> = <_>::None;
   |                              ^^^^ associated item not found in `_`

Can you mention some related threads? Or say what you’d expect from a more “complete” version of the feature? Anyways, this thread made me thing that default type parameters do cause more breakage than necessary, for example this code

#![allow(unused)]

use std::ops::Deref;

fn foo() {
    let f = |b| -> () {
        Box::deref(&b);
        *b
    };
}

compiled fine until Rust 1.48, but from 1.49 on it errors

error[E0282]: type annotations needed for `Box<(), A>`
 --> src/main.rs:6:14
  |
6 |     let f = |b| -> () {
  |              ^ consider giving this closure parameter the explicit type `Box<(), A>`, where the type parameter `A` is specified

It might be reasonable to try resolving ambiguous type variables that are used as parameters in types with parameter defaults to those defaults. Probably a nontrivial feature though. And if you have a type variable that appears in multiple parameter-with-default positions then there’s still ambiguity, so breakage is probably still possible, unless you introduce e.g. a chronological order between the default arguments so that the ones introduced earlier take precedence in resolving ambiguities… Anyways, maybe the correct interpretation for this might just be that the new trait implemenations impl<…> Deref for Box<T, A> for A != Global are what’s causing the “breakage”. And breakage in trait resolving from adding trait implementations is accepted and considered minor.

I think it's related to this thread where @quinedot helped me:

2 Likes