📜 ⬆️ ⬇️

String and & str in Rust functions


This post is dedicated to all those who are confused by the need to use to_string() to make the program compile. And I hope to shed some light on the question of why Rust has two string types, String and &str .



Functions that take strings


I want to discuss how to create interfaces that accept strings. I am a big fan of hypermedia and am passionate about creating easy-to-use interfaces. Let's start with the method that accepts String . Our search will lead us to the type std::string::String , which is not bad at first.

 fn print_me(msg: String) { println!(": {}", msg); } fn main() { let message = ", "; print_me(message); } 

We get a compilation error:
')
 expected `collections::string::String`, found `&'static str` 

It turns out that the string literal of the type &str not compatible with the type String . We need to change the type of the message variable to String so that the compilation succeeds: let message = ", ".to_string(); . It will work this way, but it's like using clone() to fix ownership-inheritance errors. Here are three reasons to change the type of the print_me argument to &str :


Example of dereference reduction


In this example, lines are created in four different ways, and they all work with the print_me function. The main point by which all this works is the transfer of values ​​by reference. Instead of passing the owned string owned_string as a String , we pass it as a &String pointer. When the compiler sees that &String passed to a function that accepts &str , it leads &String to &str . Exactly the same conversion is used when using strings with a normal and atomic reference counter. The variable string already a reference, so there is no need to use & when calling print_me(string) . With this knowledge, we no longer need to constantly call .to_string() on our code.

 fn print_me(msg: &str) { println!("msg = {}", msg); } fn main() { let string = ", "; print_me(string); let owned_string = ", ".to_string(); //  String::from_str(", ") print_me(&owned_string); let counted_string = std::rc::Rc::new(", ".to_string()); print_me(&counted_string); let atomically_counted_string = std::sync::Arc::new(", ".to_string()); print_me(&atomically_counted_string); } 

You can also use dereferencing casts with other types, such as Vec . All the same, String is just a vector of eight-byte characters. You can read more about coercion with dereferencing ( English ) in the book “ Rust Programming Language ” ( English ).

Use of structures


At this point, we should already be free from unnecessary calls to_string() . However, we may have some problems when using structures. Using existing knowledge, we could create such a structure:

 struct Person { name: &str, } fn main() { let _person = Person { name: "Herman" }; } 

We will get this error:

 <anon>:2:11: 2:15 error: missing lifetime specifier [E0106] <anon>:2 name: &str, 

Rust tries to make sure that Person cannot survive the name reference. If Person survives the name , there is a risk of the program crashing. The main goal of Rust is to prevent this. Let's make this code compile. We need to specify the time of life ( English ), or scope, so that Rust could provide us security. Typically, the lifetime is called: 'a . I do not know where this tradition came from, but we will follow it.

 struct Person { name: &'a str,' } fn main() { let _person = Person { name: "Herman" }; } 

When trying to compile, we get the following error:

 <anon>:2:12: 2:14 error: use of undeclared lifetime name `'a` [E0261] <anon>:2 name: &'a str, 

Let's think about it. We know that we want to somehow convey to the Rust compiler the idea that the Person structure should not survive the name field. So we need to declare the lifetime of the Person structure. Short searches lead us to syntax for declaring the lifetime: <'a> .

 struct Person<'a> { name: &'a str, } fn main() { let _person = Person { name: "Herman" }; } 

It compiles! Usually we implement some methods on structures. Let's add a greet method to our Person class.

 struct Person<'a> { name: &'a str, } impl Person { fn greet(&self) { println!(",   {}", self.name); } } fn main() { let person = Person { name: "Herman" }; person.greet(); } 

Now we get this error:

 <anon>:5:6: 5:12 error: wrong number of lifetime parameters: expected 1, found 0 [E0107] <anon>:5 impl Person { 

Our Person structure has a lifetime parameter, so our implementation should also have it. Let's declare the lifetime 'a in the implementation of Person like this: impl Person<'a> { . Alas, now we get this strange compilation error:

 <anon>:5:13: 5:15 error: use of undeclared lifetime name `'a` [E0261] <anon>:5 impl Person<'a> { 

In order for us to declare the lifetime, we need to specify the lifetime immediately after the impl like this: impl<'a> Person { . We compile again, we get an error:

 <anon>:5:10: 5:16 error: wrong number of lifetime parameters: expected 1, found 0 [E0107] <anon>:5 impl<'a> Person { 

Already clearer. Let's add the lifetime parameter in the description of the Person structure to its implementation like this: impl<'a> Person<'a> { . Now the program will be compiled. Here is the full working code:

 struct Person<'a> { name: &'a str, } impl<'a> Person<'a> { fn greet(&self) { println!(",   {}", self.name); } } fn main() { let person = Person { name: "Herman" }; person.greet(); } 

String or & str in structures


Now the question arises: when should String be used, and when &str in structures? In other words, when should I use a reference to another type in a structure? We should use references if our structure does not require ownership of the variable. The meaning may be slightly blurred, so I use several rules to answer this question.


 struct Person { name: String, } impl Person { fn greet(&self) { println!(",   {}", self.name); } } fn main() { let name = String::from_str("Herman"); let person = Person { name: name }; person.greet(); println!("  {}", name); // move error } 

Here I should use the link, since I will need to use a variable before placing it in the structure. A real-life example is rustc_serialize . The Encoder structure does not need to own the variable writer , which implements the type std::fmt::Write , so only borrowing is used. Actually String implements Write . In this example, when using the encode function, a variable of type String passed to the Encoder and then returned back to encode .


Now we can create a function that accepts strings in the form of &str , String or even with a reference count. We can also create structures that contain links. The lifetime of the structure is related to the references contained in it, so that the structure cannot survive the variables to which it refers, and thus lead to errors in the program. And now we have a basic understanding of when to use links within structures, and when not.

Concerning 'static


I think that you should pay attention to one more thing. We can use static static 'static lifetime (as in the first example) to make our example compile, but I wouldn’t advise you to do this:

 struct Person { name: &'static str, } impl Person { fn greet(&self) { println!(",   {}", self.name); } } fn main() { let person = Person { name: "Herman" }; person.greet(); } 

The static lifetime of 'static valid throughout the life of the program. You hardly want Person or name live for so long. (For example, static string literals compiled into the program itself have the type &'static str , that is, they live throughout the life of the program - approx. Transl.)

What else to read


Source: https://habr.com/ru/post/274485/


All Articles