Can I add extra fields when implement a trait for a struct?

When I implement my trait, I want to save some information when calling a function, which can be used for next time when use the function again, for example:

trait Push {
    fn push(&mut self, v: u8);
}

impl Push for Vec<u8> {
    fn push(&mut self, v: u8) {
        Vec::<u8>::push(&mut self, v);
    }
}

impl Push for [u8] {
    index : usize,
    fn push(&mut self, v: u8) {
        self[index++] = v;
        index+=1;
    }
}

All I know is that there's a concept called "associated constant", but never heart a "associated variable".

P.S. I considered to use the static variable, but it is not very appropriate in this case.

No, there's no practical way to do this. You'll have to save the extra data in Foo itself and declare it as part of the struct, or create some kind of wrapper around Foo that will handle storing extra.

If you can describe the context for your question in more detail we can probably suggest a specific solution.

I'm sorry I edit my question to the real question I come up with. Sorry for that if you have to edit your answer too...

What I want is that no matter what the container type it is, I have the push method for it. But in the array case, I need a index to mark where I'v pushed.

From the Rust Book:

A trait tells the Rust compiler about functionality a particular type has and can share with other types. We can use traits to define shared behavior in an abstract way.

As I understand the concept of traits: a trait defines an abstract interface to objects. A trait cannot store runtime information for you. Instead the trait gives you the ability to define common methods to access stored information in an object. An object can be a struct or enum instance.

I guess you are used to inheritance or extension of classes from other languages. Currently you cannot extend structs or enums like you could in Java or C++. You may use a programming pattern called incubation. For example:

struct Foo {
    x: i32,
    info: i32,
}

struct Bar {
    y: i32,
    info: Info,
}

trait MyTrait {
    fn my_fn(&mut self);
}

impl MyTrait for Foo {
    fn my_fn(&mut self) {
        self.info.0 += 1;
    }
}

impl MyTrait for Bar {
    fn my_fn(&mut self) {
        self.info -= 1;
    }
}

In my case I cannot modify the Foo and Bar, they are out of my source code.

And yes I'm seek some sort of inheritance design, though I know it's not easy to do in Rust...

I see. You are almost there ^^

// looks almost like a wrapper
impl Push for [u8] {
    index : usize,
    fn push(&mut self, v: u8) {
        self[index++] = v;
        index+=1;
    }
}
// use a wrapper to associate the push index with the array
fn main() {
    let array = [0u8; 10];
    assert_eq!(array, [0,0,0,0,0,0,0,0,0,0]);
    
    let mut wraped: ArrayWrapper = array.into();
    wraped.push(23);
    wraped.push(42);
    assert_eq!(wraped.array, [23,42,0,0,0,0,0,0,0,0])
}

struct ArrayWrapper {
    // care: push_index should reach every bucket of this index
    array: [u8; 10],
    // care: maximum size of u8 is 255. This example is ok because 10 < 255
    push_index: usize,
}

impl From<[u8; 10]> for ArrayWrapper {
    fn from(array: [u8; 10]) -> Self {
        Self {
            array,
            push_index: 0,
        }
    }
}

trait Push {
    fn push(&mut self, value: u8);
}

impl Push for ArrayWrapper {
    fn push(&mut self, value: u8) {
        self.array[self.push_index] = value;
        self.push_index += 1; // beware: this can overflow
    }
}

Thanks for the solution, but this got a problem, the trait Push is not implemented on the array type [u8], which leads to that if a function like fn do_something<T: Push>(v : T) will not accept an array as the parameter.

Forgive me if I'm asking too much, with this solution, is there any auto wrapping mechanism (like C++)? I guess the answer is a "no" since Rust do not have "Constructors".

Rust has the traits From and Into. The standard library implements those for several types. In for loops rust automatically uses into_iter(). I personally like that one can opt in explicitly for type conversion, but it most often doesn't do that automatically. This allows us as developers to understand the internals without too much headache about when what is transformed. It is almost always written explicitly.

The symmetric Into is implemented automatically for every type which implements From.

// implementing:
impl From<[u8; 10]> for ArrayWrapper { ... }
// implements automatically:
impl Into<ArrayWrapper> for [u8; 10] { ... }

I updated the example above so you may have a look at it. I'd suggest not to use arrays though. Arrays are very low level and bugs will creep into your code easily. See it this way: rust protected you from making a hard to find mistake: An array cannot natively store something like an insert position. It is merely a list of values stored in sequence in memory. That is why the insert position must be stored somewhere else. But then again every concrete insert position must only be used with its corresponding array. Otherwise the insert position will become inconsistent. This inconsistency will be very hard to figure out if you are not aware of it beforehand.

And BTW, don't roll your own implementation, use an existing crate like ArrayVec.

It sounds like you are running into some friction because you want to apply an inheritance-based solution to a language which deliberately doesn't permit inheritance. This would be like trying to force immutability/const-correctness onto Java - it's doable, but a massive pain.

When you want to add extra requirements and invariants on top of an existing type you'll normally make a new type which holds the original type as a private field. Then you can provide an API which presents just the functionality you need and manages the book keeping.

In this case you can also implement Deref to give people read-only access to the vector being wrapped seeing as your new type is just a smart wrapper around Vec<T>.

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.