0

I'm new to Rust. I want to write a method (trait implementation?) that takes any of a String or a string slice, treats it as immutable, and returns a new immutable string. Let's say foo is a method that doubled whatever input you give it:

let x = "abc".foo(); // => "abcabc"
let y = x.foo(); // => "abcabcabcabc"
let z = "def".to_string().foo(); // => "defdef"

In this case, I do not care about safety or performance, I just want my code to compile for a throwaway test. If the heap grows infinitely, so be it. If this requires two trait implementations, that's fine.

Petrus Theron
  • 27,855
  • 36
  • 153
  • 287
  • Do you want to have a `String` or `&str` in the end? – hellow Nov 28 '18 at 10:33
  • Whichever, as long as I can pass it back into the same function without re-casting. – Petrus Theron Nov 28 '18 at 10:35
  • In Rust we don't use the term casting. Also, *"pass it back into the same function"*: are you speaking about recursion? – hellow Nov 28 '18 at 10:37
  • It is not very clear for me, what you want. Do you want to accept a string slice, e.g. something you can read chars of but not modify it or do you want a `String` which you can modify,e.g. replace, append, prepend, ... chars. It's a huge difference. – hellow Nov 28 '18 at 10:39
  • `let x = foo("abc"); foo(x);` – Petrus Theron Nov 28 '18 at 10:39
  • Then accept a string slice and return it `fn foo(s: &str) -> &str`. Call it with `foo("ABC")` or `foo(&String::from("ABC"))` – hellow Nov 28 '18 at 10:40
  • Example: (doesn't compile) https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=a6b420adc6efd0f13701621b8a098de5 (will update Q when I understand) – Petrus Theron Nov 28 '18 at 10:45
  • Of course that doesn't work, because rust doesn't support operater overloading – hellow Nov 28 '18 at 10:46
  • @hellow, yeah I'm showing you what I'm trying to get at. If I had the solution, I'd post an answer. – Petrus Theron Nov 28 '18 at 10:47
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/184390/discussion-between-hellow-and-petrus-theron). – hellow Nov 28 '18 at 10:49
  • Possible duplicate of [What are the differences between Rust's \`String\` and \`str\`?](https://stackoverflow.com/questions/24158114/what-are-the-differences-between-rusts-string-and-str) https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=ad8b6acfe245fdde50ff623edffb8356 – Stargateur Nov 28 '18 at 11:06
  • It will depend on the kind of trait. Without more information, we can only tell you to make two `impl` blocks. If you don't know how to implement traits yet, [the book](https://doc.rust-lang.org/stable/book/second-edition/ch10-02-traits.html) is your friend. – E_net4 Nov 28 '18 at 11:10
  • 1
    Possible duplicate of [Why is it discouraged to accept a reference to a String (&String), Vec (&Vec) or Box (&Box) as a function argument?](https://stackoverflow.com/questions/40006219/why-is-it-discouraged-to-accept-a-reference-to-a-string-string-vec-vec-or) – Peter Hall Nov 28 '18 at 11:24
  • Updated my question. @E_net4iskindandwelcoming two traits is fine. I just want to reclaim usability, even at the cost of safety/perf. (in this case). – Petrus Theron Nov 28 '18 at 11:46
  • Two traits does not make sense here, since you can have just one for the intended behaviour (with multiple implementations). Note that immutability does not come to play when returning values from a function. More on this subject [here](https://stackoverflow.com/q/28587698/1233251) and [here](https://stackoverflow.com/q/50124680/1233251). – E_net4 Nov 28 '18 at 11:53
  • @E_net4iskindandwelcoming, thanks, but you suggested using two traits above. I don't really care about what the solution entails or Rust's design philosophy, I just want my code to compile and not care about strings or slices until a future version of the borrow checker can "do the right thing" and warn me about potential heap problems. I'm just trying to get productive because I have a job to do, knowingly at the cost of doing "the wrong thing". – Petrus Theron Nov 28 '18 at 11:59
  • Actually, I said two `impl` blocks, which makes two implementations rather than two traits. I can indeed answer the question, but you are _strongly_ advised to re-read the book on making and implementing traits. – E_net4 Nov 28 '18 at 12:00
  • @E_net4iskindandwelcoming my mistake on "two traits", I should have said "two trait impls". – Petrus Theron Nov 28 '18 at 12:08

2 Answers2

1

Let's say foo is a method that doubled whatever input you give it.

A trait is a perfectly good way to do this, since it makes one common behavior:

trait Foo {
    fn foo(&self) -> String;
}

... applied to multiple types:

impl Foo for String {
    fn foo(&self) -> String {
        let mut out = self.clone();
        out += self;
        out
    }
}

impl<'a> Foo for &'a str {
    fn foo(&self) -> String {
        let mut out = self.to_string();
        out += self;
        out
    }
}

Using:

let x = "abc".foo();
assert_eq!(&x, "abcabc");
let z = "shep".to_string().foo();
assert_eq!(&z, "shepshep");

Playground

The output is an owned string. Whether this value immutable or not, as typical in Rust, only comes at play at the calling site.

See also:

E_net4
  • 27,810
  • 13
  • 101
  • 139
  • Fantastic! Is there a way to return the String as immutable? The knowledge I was missing was how to write the lifetimes like, `impl<'a> Foo for &'a str`. Obviously I have *not* read the book on traits :). – Petrus Theron Nov 28 '18 at 12:07
  • Having the `String` immutable is imposed by the caller of the function instead, by keeping it in an immutable binding. The links I posted above should help you clear this out. – E_net4 Nov 28 '18 at 12:12
0

If you want a borrowed string &str at the end:

trait Foo {
    fn display(&self);
}

impl<T> Foo for T where T: AsRef<str> {
    fn display(&self) {
        println!("{}", self.as_ref());
    }
}

fn main() {
    "hello".display();
    String::from("world").display();
}

If you want an owned String:

trait Foo {
    fn display(self);
}

impl<T> Foo for T where T: Into<String> {
    fn display(self) {
        let s: String = self.into();
        println!("{}", s);
    }
}

fn main() {
    "hello".display();
    String::from("world").display();
}
Boiethios
  • 38,438
  • 19
  • 134
  • 183
  • I tried AsRef, but I got something about `T` cannot be safely shared between threads when using the result of `display()` when it has return value, i.e. `let x ="abc".display()`. Will try to provide code to replicate. – Petrus Theron Nov 28 '18 at 11:34
  • 1
    @PetrusTheron This has nothing to do with your question. That's an issue about multithreading code. – Boiethios Nov 28 '18 at 11:38
  • your example but returning a &str: https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=3fa379e099e74245ab6efe5fe497b499 . I had to add a lifetime, but I can't get it to compile. Can you help me tweak it? Assume my machine has infinite memory. – Petrus Theron Nov 28 '18 at 12:03
  • @PetrusTheron You should read more about Rust strings: `&str` vs `String`. You can find a lot of useful links, for example: https://stackoverflow.com/questions/24158114/what-are-the-differences-between-rusts-string-and-str – Boiethios Nov 28 '18 at 12:40