Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is it not possible to concatenate two Strings in Rust without taking a reference to one of them?

Tags:

rust

This works:

let hello = "Hello ".to_string();
let world = "world!";

let hello_world = hello + world;

But this doesn't:

let hello = "Hello ".to_string();
let world = "world!".to_string();

let hello_world = hello + world;

But this does:

let hello = "Hello ".to_string();
let world = "world!".to_string();

let hello_world = hello + &world;

Is that because the String needs a pointer to the original string slice of the second String? If so, why?

like image 895
dnshio Avatar asked Oct 07 '16 12:10

dnshio


2 Answers

The answer is in two parts.


The first part is that + involves using an Add trait implementation. It is implemented only for:

impl<'a> Add<&'a str> for String

Therefore, string concatenation only works when:

  • the Left Hand Side is a String
  • the Right Hand Side is coercible to a &str

Note: unlike many other languages, addition will consume the Left Hand Side argument.


The second part, therefore, is what kind of arguments can be used when a &str is expected?

Obviously, a &str can be used as is:

let hello = "Hello ".to_string();
let hello_world = hello + "world!";

Otherwise, a reference to a type implementing Deref<&str> will work, and it turns out that String does so &String works:

let hello = "Hello ".to_string();
let world = "world!".to_string();

let hello_world = hello + &world;

And what of other implementations? They all have issues.

  • impl<'a> Add<String> for &'a str requires prepending, which is not as efficient as appending
  • impl Add<String> for String needlessly consume two arguments when one is sufficient
  • impl<'a, 'b> Add<&'a str> for &'b str hides an unconditional memory allocation

In the end, the asymmetric choice is explained by Rust philosophy of being explicit as much as possible.

Or to be more explicit, we can explain the choice by checking the algorithmic complexity of the operation. Assuming that the left-hand side has size M and the right-hand side has size N, then:

  • impl<'a> Add<&'a str> for String is O(N) (amortized)
  • impl<'a> Add<String> for &'a str is O(M+N)
  • impl<'a, 'b> Add<&'a str> for &'b str is O(M+N)
  • impl Add<String> for String is O(N) (amortized)... but requires allocating/cloning the right-hand side for nothing.
like image 126
Matthieu M. Avatar answered Oct 10 '22 03:10

Matthieu M.


The standard library implements the following:

impl<'a> Add<&'a str> for String

Which is exactly what you have noticed, you can only add a &str to a String.

This has these Advantages:

  • Use a &str slice
  • Use a &String (as it dereferences to &str)
  • Keep the old String (that has been added)
like image 39
Neikos Avatar answered Oct 10 '22 05:10

Neikos