Search code examples
rusttraits

How to implement a trait for different mutabilities of self


Given is a struct with a u32 field. It has exactly one overloaded method that can be used as both setter and getter for the field.

pub struct Struct {
    value: u32
}
pub trait Trait<T> {
    fn method(&mut self, arg: T);
}
impl Trait<u32> for Struct {
    fn method(&mut self, arg: u32) {
        self.value = arg;
    }
}
impl Trait<&mut u32> for Struct {
    fn method(&mut self, arg: &mut u32) {
        *arg = self.value;
    }
}

A possible use of this structure could be as follows

fn main() {    
    let mut s = Struct { value: 0 };
    let mut v = 0;
    s.method(&mut v);
    println!("The value of s is: {}", v);
    s.method(3);
    s.method(&mut v);
    println!("The value of s is: {}", v);
}

The advantage of calling an overloaded method instead of accessing the field directly serves two reasons when the struct is used in a ffi interface. On the one hand, the method can still make modifications to the value, such as first converting a &str to a CString and then storing the *const u8 as the value, which makes the string usable for C. On the other hand, overloading the method also makes it possible to use the names given for it in C instead of writing setValue and getValue, for example. However, as you can see here, one of the two methods does not need a mutable reference to self because it does not change the value field, but because the trait requires it, it is used in both cases anyway. The struct is not only configured and then used as argument in a ffi method, but can also occur as return value of such a method, in which case it will be returned as a immutable reference and should only be read from. The customized trait implementations would look like this

impl Trait<u32> for Struct {
    fn method(&mut self, arg: u32) {
        self.value = arg;
    }
}
impl Trait<&mut u32> for Struct {
    fn method(&self, arg: &mut u32) {
        *arg = self.value;
    }
}

Obviously this won't work here because the second impl block doesn't have the same method signature as the trait. I already tried to define the self paramter as another generic parameter in the trait using the arbitrary_self_types features but unfortunately that didn't work.


Solution

  • You cannot parameterize over mutability.

    See:

    You can parameterize on self, but it will not be a method anymore, only an associated function:

    pub trait Trait<This, T> {
        fn method(this: This, arg: T);
    }
    impl Trait<&mut Self, u32> for Struct {
        fn method(this: &mut Self, arg: u32) {
            this.value = arg;
        }
    }
    impl Trait<&Self, &mut u32> for Struct {
        fn method(this: &Self, arg: &mut u32) {
            *arg = this.value;
        }
    }
    
    fn main() {
        let mut s = Struct { value: 0 };
        let mut v = 0;
        Struct::method(&s, &mut v);
        println!("The value of s is: {}", v);
        Struct::method(&mut s, 3);
        Struct::method(&s, &mut v);
        println!("The value of s is: {}", v);
    }
    

    Given the draft RFC Refined trait implementations that poses the possibility to refine a trait in its implementation, i.e. to write a less generic impl (e.g. safe method in the impl that is unsafe in the trait), it may be possible that you will be able to refine mutability in the future too (although I haven't seen discussions about that), but that will only relax the restriction when you work with a concrete instance, not with generics.

    One way or the other, I don't think this design is correct. Just use normal value() and set_value() methods, it is simple and obvious.