rusttraitschainable

Is it possible to provide default implementation of chainable trait methods?


The goal would be to have something similar to (playground):

trait T {
    fn get_mutable_attribute(&mut self) -> &mut String;
    fn forwardable_append_attribute(mut self, new_value: &str) -> Self {
        let attribute = self.get_mutable_attribute();
        attribute.push_str(new_value);
        println!("{}", attribute);
        self
    }
}

struct S {
    attribute: String,
}

impl T for S {
    fn get_mutable_attribute(&mut self) -> &mut String {
        &mut self.attribute
    }
}

fn main() {
    let s = S {
        attribute: "init".to_string(),
    }
    .forwardable_append_attribute("new_1")
    .forwardable_append_attribute("new_2")
    .forwardable_append_attribute("new_3");
}

This gives the error:

error[E0277]: the size for values of type `Self` cannot be known at compilation time
 --> src/main.rs:3:37
  |
3 |     fn forwardable_append_attribute(mut self, new_value: &str) -> Self {
  |                                     ^^^^^^^^ doesn't have a size known at compile-time
  |
  = help: unsized locals are gated as an unstable feature
help: consider further restricting `Self`
  |
3 |     fn forwardable_append_attribute(mut self, new_value: &str) -> Self where Self: std::marker::Sized {
  |                                                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: function arguments must have a statically known size, borrowed types always have a known size
  |
3 |     fn forwardable_append_attribute(&mut self, new_value: &str) -> Self {
  |                                     ^

An alternative would be to define the trait method for each object implementing the trait but that introduces duplication between all sub objects (playground):

trait T {
    fn get_mutable_attribute(&mut self) -> &mut String;
    fn forwardable_append_attribute(self, new_value: &str) -> Self;
}

struct S {
    attribute: String,
}

impl T for S {
    fn get_mutable_attribute(&mut self) -> &mut String {
        &mut self.attribute
    }
    fn forwardable_append_attribute(mut self, new_value: &str) -> Self {
        let attribute = self.get_mutable_attribute();
        attribute.push_str(new_value);
        println!("{}", attribute);
        self
    }
}

fn main() {
    let s = S {
        attribute: "init".to_string(),
    }
    .forwardable_append_attribute("new_1")
    .forwardable_append_attribute("new_2")
    .forwardable_append_attribute("new_3");
}

Solution

  • It's complaining that you can't return self, since self might not be Sized. So, you can just add a bound that Self must be Sized on the method:

    trait T {
        fn get_mutable_attribute(&mut self) -> &mut String;
        fn forwardable_append_attribute(mut self, new_value: &str) -> Self
        // note this trait bound
        where
            Self: Sized,
        {
            let attribute = self.get_mutable_attribute();
            attribute.push_str(new_value);
            println!("{}", attribute);
            self
        }
    }
    
    struct S {
        attribute: String,
    }
    
    impl T for S {
        fn get_mutable_attribute(&mut self) -> &mut String {
            &mut self.attribute
        }
    }
    
    fn main() {
        let s = S {
            attribute: "init".to_string(),
        }
        .forwardable_append_attribute("new_1")
        .forwardable_append_attribute("new_2")
        .forwardable_append_attribute("new_3");
    }
    

    Note that you could also put the Sized bound on the trait itself instead of the method, which would look like trait T: Sized {...}. You should do this if the trait only provides the chainable method, so there's no point in implementing the trait for a type that can't have the chainable method. Alternatively, instead of moving self into the function, you can take a mutable reference and return the mutable reference, removing the need for Sized:

    trait T {
        fn get_mutable_attribute(&mut self) -> &mut String;
        // note the &mut self and &mut Self
        fn forwardable_append_attribute(&mut self, new_value: &str) -> &mut Self {
            let attribute = self.get_mutable_attribute();
            attribute.push_str(new_value);
            println!("{}", attribute);
            self
        }
    }
    
    struct S {
        attribute: String,
    }
    
    impl T for S {
        fn get_mutable_attribute(&mut self) -> &mut String {
            &mut self.attribute
        }
    }
    
    fn main() {
        let s = S {
            attribute: "init".to_string(),
        }
        .forwardable_append_attribute("new_1")
        .forwardable_append_attribute("new_2")
        .forwardable_append_attribute("new_3");
    }