3

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)
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Dave Mason
  • 669
  • 6
  • 15
  • *create any extra code by chaining* — what does this mean? What code would be created, and how would chaining create code? – Shepmaster Jan 17 '19 at 17:19
  • If I returned a box, that would presumably create a box, and then dereference it later. – Dave Mason Jan 18 '19 at 02:04
  • Why are you returning `Box`es from your constructors? – Chronial Jan 18 '19 at 02:30
  • I return Boxes because it was the only way I could find to get 2 structs to implement a trait and be able to have everything using just the trait. But if there's a better way, I'd love to know about it. – Dave Mason Jan 18 '19 at 02:43
  • I tried moving the `Box` out of the constuctors to the `get` function, but same result (as I expected). – Dave Mason Jan 18 '19 at 02:57
  • Yeah, it doesn't fix your problem, but returning the types themself and boxing them at the callside is more idiomatic rust. I'm currently writing an answer explaining your actual problem. One minute :). – Chronial Jan 18 '19 at 02:58
  • I believe your question is answered by the answers of [Using impl Trait in Trait definition](https://stackoverflow.com/q/39482131/155423) / [How do I return an instance of a trait from a method?](https://stackoverflow.com/q/30661046/155423). If you disagree, please [edit] your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Jan 18 '19 at 03:08
  • Thanks Shepmaster, the second of those was why I was using `Box`es in the first place. Based on `rustc --explain E0038` it appeared that I can't do what I want. But I see that Chronial has actually done it. I don't understand why it works ... but I'm sure I can eventually do so. So, no, I don't think those links answer the question. Sorry if I was unclear. – Dave Mason Jan 18 '19 at 04:03

2 Answers2

2

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.

Chronial
  • 66,706
  • 14
  • 93
  • 99
  • Thanks! But I'm quite confused about the details. From your preliminary comment, I assumed it was `put` that you were going to `impl` for `Box` but for that you defined a helper function. The `put` function itself is marked as Self:Sized, which E0038 says: "you can add a `where Self: Sized` bound on them to mark them as explicitly unavailable to trait objects. The functionality will still be available to all other implementers, including `Box` which is itself sized (assuming you `impl Trait for Box`)" but it is available. Strange. – Dave Mason Jan 18 '19 at 04:12
  • Then, I don't understand why the function implemented for `Box` - does it simply trigger some recognition in the compiler, because I seem to be able to define all sorts of other "virtual" functions. – Dave Mason Jan 18 '19 at 04:20
  • In a way I did implement `put` for `Box`. It has a default implementation in the trait itself. That implementation is not available on unsized types (i.e. trait objects), but it is available on sized objects.(i.e. `Box`). Does this all make sense to you when you move the implementation of `put` from the trait to the `impl RequestInfo for Box` block? – Chronial Jan 18 '19 at 04:20
  • @Chronial Ahh... any function that doesn't have an implementation in the the trait, needs one in the `Box` block, even if all it does is dispatch to the ref'ed struct (in fact, that's what they all should be). So the chaining adds an extra function call for each chain step (presumably the `as_ref()` gets inlined). – Dave Mason Jan 18 '19 at 05:14
  • Correct. But the call to `pub` will also inlined (it is not using dynamic dispatch). I just tested this, and the chained version generates exactly the same assembler as the unchained version: https://godbolt.org/z/CTZLNX – Chronial Jan 18 '19 at 05:47
2

I gave this some more thought and you can disregard my previous answer. That solution is unnecessarily complex. I got way to focused on returning &mut Self from put, even though that wasn't asked for at all. You can just return &mut RequestInfo from your put method and you're fine. The only price you pay is that you can't have a default implementation for put anymore.

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

struct LoggedOut {}
impl RequestInfo for LoggedOut {
    fn put(self: &mut Self, string: String) -> &mut dyn RequestInfo {
        self
    }
}

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

fn get(flag: bool) -> Box<dyn RequestInfo> {
    if flag {Box::new(LoggedIn::new())} else {Box::new(LoggedOut{})}
}


fn main() {
    let mut info = get(false);
    info.put("abc".to_string()).put("def".to_string());
}
Chronial
  • 66,706
  • 14
  • 93
  • 99
  • Thanks! I don't know why someone down-voted this, but this is much nicer. I'll use this a lot as I spend my non-Rust time coding in Smalltalk, so I like objects (even trait objects having no inheritance), and I like cascading calls. – Dave Mason Jan 22 '19 at 14:52
  • You're welcome :). But you should probably be aware that this uses dynamic dispatch which will give you a tiny performance penalty on each chained call. But this should be negligible in most scenarios. – Chronial Jan 22 '19 at 22:42