Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Herman J. Radtke III

Creating a Rust function that accepts String or &str

Russian Translation

In mylast post we talked a lot about using&str as the preferred type for functions accepting a string argument. Towards the end of that post there was some discussion about when to useString vs&str in astruct. I think this advice is good, but there are cases where using&str instead ofString is not optimal. We need another strategy for these use cases.

A struct Containing Strings

Consider thePerson struct below. For the sake of discussion, let's sayPerson has a real need to own thename variable. We choose to use theString type instead of&str.

structPerson {name: String,}

Now we need to implement anew() function. Based on my last blog post, we prefer a&str:

implPerson {fnnew(name: &str) -> Person {        Person { name: name.to_string() }    }}

This works as long as we remember to call.to_string() inside of thenew() function. However, the ergonomics of this function are less than desired. If we use a string literal, then we can make a newPerson likePerson.new("Herman"). If we already have aString though, we need to ask for a reference to theString:

let name = "Herman".to_string();let person = Person::new(name.as_ref());

It feels like we are going in circles though. We had aString, then we calledas_ref() to turn it into a&str only to then turn it back into aString inside of thenew() function. We could go back to using aString likefn new(name: String) -> Person {, but that means we need to force the caller to use.to_string() whenever there is a string literal.

Into conversions

We can make our function easier for the caller to work with by using theInto trait. This trait will can automatically convert a&str into aString. If we already have aString, then no conversion happens.

structPerson {name: String,}implPerson {fnnew<S: Into<String>>(name: S) -> Person {        Person { name: name.into() }    }}fnmain() {let person = Person::new("Herman");let person = Person::new("Herman".to_string());}

This syntax fornew() looks a little different. We are usingGenerics andTraits to tell Rust that some typeS must implement the traitInto for typeString. TheString type implementsInto<String> as noop because we already have aString. The&str type implementsInto<String> by using the same.to_string() method we were originally doing in thenew() function. So we aren't side-stepping the need for the.to_string() call, but we are taking away the need for the caller to do it. You might wonder if usingInto<String> hurts performance and the answer is no. Rust usesstatic dispatch and the concept ofmonomorphization to handle all this during the compiler phase.

Don't worry if things likestatic dispatch andmonomorphization are confusing. You just need to know that using the syntax above you can create functions that accept bothString and&str. If you are thinking thatfn new<S: Into<String>>(name: S) -> Person { is a lot of syntax, it is. It is important to point out though that there is nothing special aboutInto<String>. It is just a trait that is part of the Rust standard library. You could implement this trait yourself if you wanted to. You can implement similar traits you find useful and publish them oncrates.io. All this userland power is what makes Rust an awesome language.

Another Way To Write Person::new()

Thewhere syntax also works and may be easier to read, especially if the function signature becomes more complex:

structPerson {name: String,}implPerson {fnnew<S>(name: S) -> Personwhere S: Into<String> {        Person { name: name.into() }    }}

Related

May, 6 2015

[8]ページ先頭

©2009-2025 Movatter.jp