How to restrict a parameter by trait and type

I'm new to traits and struggling with what might be an impossible ask.

I want a collection/struct (TroveHistory) which is generic on a type which must implement the trait Trove, but I don't want the collection to accept any type which implements Trove. So put together some test code to try and figure out if this is possible.

In the following code I have the trait Trove, two structs which implement it (Website and Webpage) and a collection TroveHistory. I want TroveHistory to be generic so I anyone can use it to hold a type which implements Trove.

I'm not sure how to specify that it should only be generic for something which implements Trove (maybe struct TroveHistory<T: Trove>) and that it should only accept one type rather than any type which implements Trove.

I've tried a few variations on the generic such as <T> (in the code below) <T: Trove> and so on but can't get everything right in both the definitions and the test function.

Here's one attempt to test this which has two compilation errors (commented) and at the bottom a test function which also explains what I'm trying to achieve.

/// A snapshot of a generic item, struct or collection which has a
/// type and can be stored or loaded from storage.
pub trait Trove {
    fn trove_type(&self) -> u32;
    fn trove_store(&self) -> bool;
    fn trove_load() -> Self;
}

/// An ordered collection of Trove objects.
pub struct TroveHistory<T: Trove> {
    troves: Vec<T>,
}

impl TroveHistory<T> {
    // error above: cannot find type `T` in this scope
    pub fn new() -> TroveHistory<T> {
        // error above: cannot find type `T` in this scope
        TroveHistory { troves: vec![] }
    }

    pub fn trove_add(&self, trove: &impl Trove) {
        self.troves.push(trove);
    }
}

// A struct which will can be held in a TroveHistory
pub struct Website {
    content: String,
}

impl Website {
    pub fn new() -> Website {
        Website {
            content: String::from(""),
        }
    }
    pub fn content(&self) -> String {
        return self.content.clone();
    }
}

impl Trove for Website {
    fn trove_type(&self) -> u32 {
        1
    }
    fn trove_store(&self) -> bool {
        true
    }
    fn trove_load() -> Self {
        Website {
            content: String::from("Some content"),
        }
    }
}

// A second struct which will can be held in a TroveHistory
pub struct WebPage {
    content: String,
}

impl WebPage {
    pub fn new() -> WebPage {
        WebPage {
            content: String::from(""),
        }
    }
    pub fn content(&self) -> String {
        return self.content.clone();
    }
}

impl Trove for WebPage {
    fn trove_type(&self) -> u32 {
        1
    }
    fn trove_store(&self) -> bool {
        true
    }
    fn trove_load() -> Self {
        WebPage {
            content: String::from("Some content"),
        }
    }
}

#[test]
pub fn test() {
    println!("TEST!");
    let ws = Website::new();
    let wp = WebPage::new();

    let history = TroveHistory::new();
    history.trove_add(&ws);
    // Ideally the next line should not compile because I want
    // pub struct TroveHistory<T: Trove> to restrict the trove
    // parameter to TroveHistory<Website> rather than to
    // any type which implement Trove. But at the moment
    // I can't get it to compile due to errors indicated above
    // at:
    // impl TroveHistory<T> {
    // pub fn new() -> TroveHistory<T> {

    history.trove_add(&wp);
}

you need to do impl<T: Trove> TroveHistory. Try your test again because i'm pretty sure it shouldn't compile and behave as you would like.
You also need to change your trove_add to take T (or &T) as a second parameter instead of &impl Trove.
then you have one T that that instance of TroveHistory will be valid for.

1 Like

Here is a corrected version of your impl, which will compile:

impl<T: Trove> TroveHistory<T> {
    pub fn new() -> Self {
        TroveHistory { troves: vec![] }
    }

    pub fn trove_add(&mut self, trove: T) {
        self.troves.push(trove);
    }
}

You’ll need to adjust the usage of trove_add to remove the incorrect &s.

Besides the necessary changes, I also wrote the return type of new() as Self instead of TroveHistory<T>. This is exactly the same thing, but eliminates the possible mistake of writing it differently — it would be likely incorrect for a new() to return a type different from the Self type, so it’s better to write the code so that it obviously doesn’t.

1 Like

Thank you both for your amazingly fast and helpful responses!

For anyone who needs it, here is the code which now does what I want:

/// A snapshot of a generic item, struct or collection which has a
/// type and can be stored or loaded from storage.
pub trait Trove {
    fn trove_type(&self) -> u32;
    fn trove_store(&self) -> bool;
    fn trove_load() -> Self;
}

/// An ordered collection of Trove objects.
pub struct TroveHistory<T: Trove> {
    troves: Vec<T>,
}

impl<T: Trove> TroveHistory<T> {
    pub fn new() -> Self {
        TroveHistory { troves: vec![] }
    }

    pub fn trove_add(&mut self, trove: T) {
        self.troves.push(trove);
    }
}

// A struct which will can be held in a TroveHistory
pub struct Website {
    content: String,
}

impl Website {
    pub fn new() -> Website {
        Website {
            content: String::from(""),
        }
    }
    pub fn content(&self) -> String {
        return self.content.clone();
    }
}

impl Trove for Website {
    fn trove_type(&self) -> u32 {
        1
    }
    fn trove_store(&self) -> bool {
        true
    }
    fn trove_load() -> Self {
        Website {
            content: String::from("Some content"),
        }
    }
}

// A second struct which will can be held in a TroveHistory
pub struct WebPage {
    content: String,
}

impl WebPage {
    pub fn new() -> WebPage {
        WebPage {
            content: String::from(""),
        }
    }
    pub fn content(&self) -> String {
        return self.content.clone();
    }
}

impl Trove for WebPage {
    fn trove_type(&self) -> u32 {
        1
    }
    fn trove_store(&self) -> bool {
        true
    }
    fn trove_load() -> Self {
        WebPage {
            content: String::from("Some content"),
        }
    }
}

#[test]
pub fn test() {
    println!("TEST!");
    let ws = Website::new();
    let wp = WebPage::new();

    let history = TroveHistory::<Website>::new();
    history.trove_add(ws);

    // The next line does not compile - which is what I want!
    history.trove_add(wp);
}