A Rust function that takes a string argument might look like this:

fn foo(s: &str) { ... }

This works well, if the the function merely uses the string. If the string is needed after the function returns, e.g., because it's stored somewhere, either the function needs to manage lifetimes, or make a copy. Correctly managed lifetimes are good for avoiding unnecessary copies, but they can be tricky to manage.

Sometimes a new value is necessary. There are three option:

  • fn foo(s: &str)
  • fn foo(s: String)
  • fn foo(s: impl Into<String>)

In the first case, the function creates the new value itself. This is convenient for the caller, but can cause an unncessary new value to be made if the caller already has one it can give. This matters if there's a need to manage memory tightly.

In the second case the caller creates the new value, if a new one is needed. This avoids creating unnecessary new values, but is inconvenient to the caller.

In the third case, we make use of Rust monomorphism and traits and have the Rust compiler generate code that creates a new value, if one is needed. This makes it possible to be both convenient and manage memory tightly.

I learnt this trick from Daniel.