Trait method that supports both &str and String as parameter

Hello,
I would like to support both &str and String as function parameter.
Currently, I am using this signature:

But this doesn't work anymore with Trait:

trait History {
    fn add(&mut self, s: &str);
    fn add_(&mut self, s: dyn AsRef<str> + Into<String>);
}

struct Editor<H: History> {
    history: H,
}

struct NoHistory {}

impl History for NoHistory {
    fn add(&mut self, _: &str) {}
    fn add_(&mut self, _: dyn AsRef<str> + Into<String>) {}
}

fn main() {
    let mut editor = Editor {
        history: NoHistory {},
    };
    editor.history.add("hello");
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0225]: only auto traits can be used as additional traits in a trait object
 --> src/main.rs:3:44
  |
3 |     fn add_(&mut self, s: dyn AsRef<str> + Into<String>);
  |                               ----------   ^^^^^^^^^^^^ additional non-auto trait
  |                               |
  |                               first non-auto trait
  |
  = help: consider creating a new trait with all of these as supertraits and using that trait here instead: `trait NewTrait: AsRef<str> + Into<String> {}`
  = note: auto-traits like `Send` and `Sync` are traits that have special properties; for more information on them, visit <https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits>

error[E0225]: only auto traits can be used as additional traits in a trait object
  --> src/main.rs:14:44
   |
14 |     fn add_(&mut self, _: dyn AsRef<str> + Into<String>) {}
   |                               ----------   ^^^^^^^^^^^^ additional non-auto trait
   |                               |
   |                               first non-auto trait
   |
   = help: consider creating a new trait with all of these as supertraits and using that trait here instead: `trait NewTrait: AsRef<str> + Into<String> {}`
   = note: auto-traits like `Send` and `Sync` are traits that have special properties; for more information on them, visit <https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits>

For more information about this error, try `rustc --explain E0225`.
error: could not compile `playground` due to 2 previous errors

One solution is to have two distinct functions:

Do you know a better alternative ?
Thanks.

Just use the type &str instead of making your life unnecessarily difficult. The user can pass a string by putting an & in front of the variable name.

9 Likes

Also, you simply cannot have Into<String> as a trait object.

1 Like

In fact, I actually need a String (VecDeque<String>::push_back) but only if some conditions are true.
And alternative libraries choose either String:

or &str:

You can call .to_string() on a &str to get a String.

I know but if you have a String and you call add then you make a copy. So this is not zero cost abstraction.

let line: String = editor.readline("> ")?;
editor.history.add(&line); // copy here while calling `.to_string()`

I don't think that matters at all for what it seems like you are doing, but if you must, add a method that takes &str and one that takes String, or only one that takes String.

Each implementation can have one of them call the other, depending on whether they can reuse the allocation or not.

2 Likes

Ok,
This is exactly what I have done but I have been looking for a better solution:

Thanks.

Okay, well, to actually answer the question, this is how you can write the code you had originally:

trait History {
    fn add<T>(&mut self, s: T)
    where
        T: Into<String> + AsRef<str>;
}

However this makes it so History can no longer be used as a trait object.

But I do use History as a trait object (see playground example)...

You could try something like this:

use std::borrow::Cow;

trait History {
    fn add_str(&mut self, s: &str);
    fn add_owned(&mut self, s: String);
}

trait HistoryExt {
    fn add<'a, T: Into<Cow<'a, str>>>(&mut self, s: T);
}

impl<H: History + ?Sized> HistoryExt for H {
    fn add<'a, T: Into<Cow<'a, str>>>(&mut self, s: T) {
        match s.into() {
            Cow::Owned(s) => self.add_owned(s),
            Cow::Borrowed(s) => self.add_str(s),
        }
    }
}

As long as the extension trait is in scope, the add method can be used on anything that implements History.

2 Likes

I do not know your use case, but if you want to be efficient (namely: you want to be able to use &str when you need and get String when you want to copy) you need to use Cow.

Alternatively you can device trait that describes how history element is added, and implement it for both &str and String. In future you might want to support something better than just plain string types after all

struct Editor; //dummy Editor

trait HistoryElement {
    add_to_history(&self, history: Editor);
}

impl HistoryElement for String {
}

impl<'a> HistoryElement for &'a str {
}
1 Like

Before introducing History trait, implementation could take anything that implements AsRef<str> + Into<String>.

how about Cow<str>?

Indeed !

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.