first_name<'a>(name: &'a str) -> &'a str
you promised the caller that the output is built directly from the input slice. That is your choice. I'll show you later that you didn't have to.Similarly by writing this in Go:
func firstName(name string) string
you promised the function accepts a string.Then you want to change the semantics (the contract) of the function, break your promise and you complain you have to change the signature. You can't do that in any statically typed language.
In Go's case, if you suddently wanted to change the memory representation of name to something else than string, e.g. to a name struct:
func firstName(first_name name) name
then obviously you have to change the signature. This is the same problem.If you don't want your caller to be affected by lifetimes, just don't specify them in the signature:
fn first_name(name: String) -> String
this is perfectly fine in Rust.You may say it might be slow because it forces a copy, and forces a particular string implementation. So as I said, Rust gives you tools to abstract out the implementation details:
fn first_name(name: impl AsRef<str>) -> impl AsRef<str> {
// all are correct:
// return name;
// return "foo";
// return String::from("foo");
}
The flexibility goes actually much further than just relaxing the lifetimes. With this signature I actually can change the name representation from String to any type that can be exposed as a slice, with no additional runtime penalty like virtual calls, which you'd need otherwise in Go/Java.> I just think it is important to be realistic about the downsides as well as the upsides of Rust's ownership system.
So the downside is that it offers you more choices and allows to express more constraints in the signatures, and you can choose wrong. I guess that's quite ok in a general purpose language that wants to be applicable to many different niches.