简体   繁体   中英

Can I implement a trait which adds information to an external type in Rust?

I just implemented a simple trait to keep the history of a struct property:

fn main() {
    let mut weight = Weight::new(2);
    weight.set(3);
    weight.set(5);
    println!("Current weight: {}. History: {:?}", weight.value, weight.history);
}

trait History<T: Copy> {
    fn set(&mut self, value: T);
    fn history(&self) -> &Vec<T>;
}

impl History<u32> for Weight {
    fn set(&mut self, value: u32) {
        self.history.push(self.value);
        self.value = value;
    }
    fn history(&self) -> &Vec<u32> {
        &self.history
    }
}

pub struct Weight {
    value: u32,
    history: Vec<u32>,
}

impl Weight {
    fn new(value: u32) -> Weight {
        Weight {
            value,
            history: Vec::new(),
        }
    }
}

I don't expect this is possible, but could you add the History trait (or something equivalent) to something which doesn't already have a history property (like u32 or String ), effectively tacking on some information about which values the variable has taken?

No. Traits cannot add data members to the existing structures. Actually, only a programmer can do that by modifying the definition of a structure. Wrapper structures or hash-tables are the ways to go.

No, traits can only contain behavior, not data. But you could make a struct.

If you could implement History for u32 , you'd have to keep the entire history of every u32 object indefinitely, in case one day someone decided to call .history() on it. (Also, what would happen when you assign one u32 to another? Does its history come with it, or does the new value just get added to the list?)

Instead, you probably want to be able to mark specific u32 objects to keep a history. A wrapper struct, as red75prime's answer suggests, will work:

mod hist {
    use std::mem;

    pub struct History<T> {
        value: T,
        history: Vec<T>,
    }

    impl<T> History<T> {
        pub fn new(value: T) -> Self {
            History {
                value,
                history: Vec::new(),
            }
        }

        pub fn set(&mut self, value: T) {
            self.history.push(mem::replace(&mut self.value, value));
        }

        pub fn get(&self) -> T
        where
            T: Copy,
        {
            self.value
        }

        pub fn history(&self) -> &[T] {
            &self.history
        }
    }
}

It's generic, so you can have a History<u32> or History<String> or whatever you want, but the get() method will only be implemented when the wrapped type is Copy .* Your Weight type could just be an alias for History<u32> . Here it is in the playground.

Wrapping this code in a module is a necessary part of maintaining the abstraction. That means you can't write weight.value , you have to call weight.get() . If value were marked pub , you could assign directly to weight.value (bypassing set ) and then history would be inaccurate.

As a side note, you almost never want &Vec<T> when you can use &[T] , so I changed the signature of history() . Another thing you might consider is returning an iterator over the previous values (perhaps in reverse order) instead of a slice.


* A better way of getting the T out of a History<T> is to implement Deref and write *foo instead of foo.get() .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM