Search code examples
genericsruststructtypesdefault

error[E0308]: mismatched types , Default generic type is invalid


I've tried specifying the type in two different places, but the compiler still throws an error.

use serde::Serialize;

// 1: try to set default type.
struct Student<T = Option<String>> {
    age: i32,
    data: T
}

impl<T> Student<T> where T: Serialize {
    
    fn new(age: i32) -> Self {
        Student {
            age, 
            data: <Option<String>>::None // 2. try to set default type
        }
    }
    
    
    // fn new_with_data(age: i32, data: T) -> Self {
    //     Student {
    //         age,
    //         data
    //     }
    // }
}


fn main() {
 
    let stu1 = Student::new(123);
    
    //let stu2 = Student::new_with_data(123, <Option<()>>::None);
    
}

got error:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
  --> src/main.rs:13:19
   |
8  | impl<T> Student<T> where T: Serialize {
   |      - this type parameter
...
13 |             data: <Option<String>>::None
   |                   ^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `T`, found enum `std::option::Option`
   |
   = note: expected type parameter `T`
                        found enum `std::option::Option<std::string::String>`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` due to previous error

the play link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=749e31fae6091226b99e233a016ca0c8


Solution

  • The issue is while you set a default type, the impl block does not require that T is the default. <Option<String>>::None is only a valid value if the type is Option<String>, but this does not require that to be the case.

    /// `T` is unconstrained and may or may not be `Option<T>`
    impl<T> Student<T> {}
    
    /// This implements student for only one specific generic type (`Option<String>`). This
    /// functionality will not be available for other generic types.
    impl Student<Option<String>> {}
    
    /// Here we don't include the generic so it implements for only the default generic. This is
    /// equivalent to the previous version, but the generic is assumed to be the default value. 
    impl Student {}
    

    What I suspect you want to do is make the data generic. Instead of making the the Option generic, we just make the data it holds generic. We can still set a default value without running into this issue.

    struct Student<T = String> {
        age: i32,
        /// Some optional generic data
        data: Option<T>
    }
    
    impl<T> Student<T> where T: Serialize {
        fn new(age: i32) -> Self {
            Student {
                age, 
                data: None,
            }
        }
        
        
        fn new_with_data(age: i32, data: T) -> Self {
            Student {
                age,
                data: Some(data),
            }
        }
    }
    

    Another option would be to keep the type the same, but split the impl to two parts. This way we can require the use of new_with_data if the data is not an Option, but still allow the usage of new in some cases.

    struct Student<T = Option<String>> {
        age: i32,
        data: T
    }
    
    impl<T> Student<Option<T>> where T: Serialize {
        fn new(age: i32) -> Self {
            Student {
                age, 
                data: None,
            }
        }
    }
    
    impl<T> Student<T> where T: Serialize {
        fn new_with_data(age: i32, data: T) -> Self {
            Student { age, data }
        }
    }