How to set a default value for a generic field

This example is copied from https://doc.rust-lang.org/rust-by-example/generics/impl.html

struct GenVal<T>{
    gen_val: T
}

// impl of GenVal for a generic type `T`
impl <T> GenVal<T> {
    fn new() -> Self {
        Self {
            gen_val: 1,
        }
    }
    fn set_gen_val(&mut self, gen_val: T) {
        self.gen_val = gen_val;
    }
}

fn main() {
    // let y = GenVal { gen_val: 3i32 };
    let mut y = GenVal::new(1);
    y.set_gen_val(2);

    println!("{}", y.gen_val);
}

This code doesn't compile because gen_val is defined as a generic type, I know I can write

fn new(init_value: T) -> Self {
    Self {
        gen_val: init_value,
    }
}

But can I just use new directly and create the default value inside the new method?

First import this trait and change the impl to impl<T: One> GenVal<T> {

You now only allow types that have one as a value.

In the new function you just type gen_val: T::one()

Thanks, your solution works if the gen_val field is a number, but if in a much more generic situation like:

struct GenVal<T: T1>{
    gen_val: T
}

trait T1 {}

#[derive(Default)]
struct S1 {}

impl T1 for S1 {}

// impl of GenVal for a generic type `T`
impl <T: T1> GenVal<T> {
    fn new() -> Self {
        Self {
            gen_val: S1::default(),
        }
    }
    fn set_gen_val(&mut self, gen_val: T) {
        self.gen_val = gen_val;
    }
}

fn main() {
    let y = GenVal::new();
}

gen_val is bound to be T1, how can I fix this code? create a One trait for S1 ?

Thanks, problem solved using

struct GenVal<T: T1>{
    gen_val: T
}

trait T1 {
    fn one() -> Self;
}

#[derive(Default)]
struct S1 {}

impl T1 for S1 {
    fn one() -> Self {
        S1 {}
    }
}

// impl of GenVal for a generic type `T`
impl <T: T1> GenVal<T> {
    fn new() -> Self {
        Self {
            gen_val: T::one(),
        }
    }
    fn set_gen_val(&mut self, gen_val: T) {
        self.gen_val = gen_val;
    }
}

fn main() {
    let y: GenVal<S1> = GenVal::new();
}

Sadly it's not real generic, because I can't set the gen_val at runtime.

struct GenVal<T: T1>{
    gen_val: T,
}

trait T1 {
    fn one() -> Self;
}

#[derive(Default)]
struct S1 {}

impl T1 for S1 {
    fn one() -> Self {
        S1 {}
    }
}


#[derive(Default)]
struct S2 {}

impl T1 for S2 {
    fn one() -> Self {
        S2 {}
    }
}

// impl of GenVal for a generic type `T`
impl <T: T1> GenVal<T> {
    fn new() -> Self {
        Self {
            gen_val: T::one(),
        }
    }
    fn set_gen_val(&mut self, gen_val: T) {
        self.gen_val = gen_val;
    }
}

fn main() {
    let mut y: GenVal<S1> = GenVal::new();
    y.set_gen_val(S2::default());
}

This should work because both S1 and S2 implement T1, so they should be replacable, why this code failed to compile? Any other way to implement the set_gen_val method so that we can create change gen_val to another concrete struct?

The problem here is that the type of y is GenVal<S1>, so you can only put S1 in it. It's just like a Vec, you don't just have a Vec that you can put whatever in, you might have a Vec<i32> that you can only put integers of the type i32 in.

It is not possible to put any kind of T1 directly inside GenVal, because then it is not possible for the compiler to figure out how large the GenVal struct is (in bytes). The solution to this is to use a Box, which the T1 on the heap, and puts an 8 byte pointer inside the GenVal struct.

Using a box looks like this:

struct GenVal {
    gen_val: Box<dyn T1>,
}

trait T1 {
    fn one() -> Self where Self: Sized;
}

#[derive(Default)]
struct S1 {}

impl T1 for S1 {
    fn one() -> Self {
        S1 {}
    }
}

#[derive(Default)]
struct S2 {}

impl T1 for S2 {
    fn one() -> Self {
        S2 {}
    }
}

impl GenVal {
    fn new<T: T1 + 'static>() -> Self {
        Self {
            gen_val: Box::new(T::one()),
        }
    }
    fn set_gen_val<T: T1 + 'static>(&mut self, gen_val: T) {
        self.gen_val = Box::new(gen_val);
    }
}

fn main() {
    let mut y = GenVal::new::<S1>();
    y.set_gen_val(S2::default());
}

Note that GenVal is no longer generic, because you don't fix it on any type any longer.

Regarding the 'static you see on the methods on GenVal, it is because you might define a type like this:

struct SWithLifetime<'a> {
    pointer_to_somewhere_else: &'a i32,
}

This type contains a pointer, and therefore can't outlive that pointer. If you were to put it in GenVal, you would loose the information regarding how long it may live, and therefore we must require 'static, which disallows things with lifetimes like that.

This wasn't an issue before, because the type would just look like this: GenVal<SWithLifetime<'a>>, so the information wasn't lost.

Got it, thanks.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.