Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I return a trait from an implementation so I can chain calls?

Tags:

rust

traits

I have a trait with a couple of implementations, and I want to return the object so I can chain calls.

pub trait RequestInfo {
    fn logged_in(&self) -> bool;
    fn put(&mut self, string: String) -> RequestInfo {
        self
    }
}

struct LoggedOut {}
impl LoggedOut {
    fn new() -> Box<RequestInfo> {
        Box::new(LoggedOut {})
    }
}
impl RequestInfo for LoggedOut {
    fn logged_in(&self) -> bool {
        false
    }
}

struct LoggedIn {
    output: Vec<String>,
}
impl LoggedIn {
    fn new() -> Box<RequestInfo> {
        Box::new(LoggedIn { output: Vec::new() })
    }
}
impl RequestInfo for LoggedIn {
    fn logged_in(&self) -> bool {
        true
    }
    fn put(&mut self, string: String) -> impl RequestInfo {
        self.output.push(string);
        self
    }
}

fn main() {
    let mut info = LoggedIn::new();
    info.put("abc".to_string()).put("def".to_string());
}

I get errors:

error[E0562]: `impl Trait` not allowed outside of function and inherent method return types
  --> src/main.rs:32:42
   |
32 |     fn put(&mut self, string: String) -> impl RequestInfo {
   |                                          ^^^^^^^^^^^^^^^^

error[E0308]: mismatched types
 --> src/main.rs:4:9
  |
3 |     fn put(&mut self, string: String) -> RequestInfo {
  |                                          ----------- expected `(dyn RequestInfo + 'static)` because of return type
4 |         self
  |         ^^^^ expected trait RequestInfo, found &mut Self
  |
  = note: expected type `(dyn RequestInfo + 'static)`
             found type `&mut Self`

error[E0277]: the size for values of type `(dyn RequestInfo + 'static)` cannot be known at compilation time
 --> src/main.rs:3:42
  |
3 |     fn put(&mut self, string: String) -> RequestInfo {
  |                                          ^^^^^^^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `(dyn RequestInfo + 'static)`
  = note: to learn more, visit <https://doc.rust-lang.org/book/second-edition/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
  = note: the return type of a function must have a statically known size

The only thing that might work is to Box self, like I do in the new() functions, but I don't want to create any extra code by chaining... which is really just a convenience anyway.

Returning &mut Self and using Box<impl RequestInfo> almost works.... except that I have a function that returns either a LoggedIn object or a LoggedOut object, so here's revised code:

pub trait RequestInfo {
    fn logged_in(&self) -> bool;
    fn put(&mut self, string: String) -> &mut Self {
        self
    }
}

struct LoggedOut {}
impl LoggedOut {
    fn new() -> Box<impl RequestInfo> {
        Box::new(LoggedOut {})
    }
}
impl RequestInfo for LoggedOut {
    fn logged_in(&self) -> bool {
        false
    }
}

struct LoggedIn {
    output: Vec<String>,
}
impl LoggedIn {
    fn new() -> Box<impl RequestInfo> {
        Box::new(LoggedIn { output: Vec::new() })
    }
}
impl RequestInfo for LoggedIn {
    fn logged_in(&self) -> bool {
        true
    }
    fn put(&mut self, string: String) -> &mut Self {
        self.output.push(string);
        self
    }
}

fn get(flag: bool) -> Box<impl RequestInfo> {
    if flag {
        return LoggedIn::new();
    }
    LoggedOut::new()
}

fn main() {
    let mut info = get(true);
    info.put("abc".to_string()).put("def".to_string());
}

And it gives the following error (earlier in the function it returned a LoggedIn object):

error[E0308]: mismatched types
  --> src/main.rs:42:5
   |
42 |     LoggedOut::new()
   |     ^^^^^^^^^^^^^^^^ expected opaque type, found a different opaque type
   |
   = note: expected type `std::boxed::Box<impl RequestInfo>` (opaque type)
              found type `std::boxed::Box<impl RequestInfo>` (opaque type)
like image 671
Dave Mason Avatar asked Oct 14 '25 18:10

Dave Mason


1 Answers

And now to the final step: You will have noticed that traits with methods that return Self can not be used as trait objects. That link has a solution to your problem: mark that method with where Self: Sized, so it doesn't appear on your trait object. But then you can't do your chaining with trait objects. This can be solved by implementing the method on Box<dyn RequestInfo> – which is not a trait object, but a Box. So, putting this all together:

pub trait RequestInfo {
    fn logged_in(&self) -> bool;
    fn put(&mut self, string: String) -> &mut Self
    where Self: Sized {
        self.put_internal(string);
        self
    }
    fn put_internal(&mut self, string: String) {}
}
impl RequestInfo for Box<dyn RequestInfo> {
    fn logged_in(&self) -> bool {
        self.as_ref().logged_in()
    }
}

struct LoggedOut {}
impl RequestInfo for LoggedOut {
    fn logged_in(&self) -> bool {false}
}

struct LoggedIn {output: Vec<String>}
impl LoggedIn {
    fn new() -> LoggedIn {
        LoggedIn { output: Vec::new() }
    }
}
impl RequestInfo for LoggedIn {
    fn logged_in(&self) -> bool {true}
    fn put_internal(&mut self, string: String) {
        self.output.push(string);
   }
}
fn get(flag: bool) -> Box<dyn RequestInfo> {
    if flag {Box::new(LoggedIn::new())} else {Box::new(LoggedOut{})}
}

fn main() {
    let mut info = get(true);
    info.put("abc".to_string()).put("def".to_string());
}

You will have to decide if all of this is worth it for some chaining.

like image 107
Chronial Avatar answered Oct 18 '25 10:10

Chronial



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!