24 days from Node.js to Rust

preface

The first hurdle encountered in Rust strings stems from an unexpected result, a string literal “Hi!” Instead of an instance of String, the following code reveals the result:

fn main() {
  print_type_of(&"Hi!");
  print_type_of(&String::new());
}

fn print_type_of<T>(_: &T) {
  println!("Type is: {}", std::any::type_name::<T>())
}
Copy the code
$ cargo run
Type is: &str
Type is: alloc::string::String
Copy the code

Interestingly, String literals are not instances of strings in JavaScript either:

"Hi!"= = ="Hi!";
// > true

"Hi!"= = =new String("Hi!");
// > false
Copy the code

More examples:

typeof "Hi!";
// > "string"

typeof new String("Hi!");
// > "object"

typeof String("Hi!");
// > "string"

"Hi!"= = =String("Hi!");
// > true

String("Hi!") = = =new String("Hi!");
// > false
Copy the code

JavaScript essentially obliterates the distinction between string literals and strings. If you want to call a method on a string literal, JavaScript will automatically convert it without having to convert it to a literal. Rust has a similar mechanism

reading

There is a lot of information about strings, and the official documentation should be read carefully

  • Strings in the Rust docs
  • Why Are There Two Types of Strings In Rust?
  • How do I convert a &str to a String in Rust?
  • Rust String vs str slices
  • Rust: str vs String
  • String vs &str in Rust

The body of the

&str

String literals are essentially references to a string slice, which means that they are actually substring Pointers to string data. The Rust compiler will store all of our string literals in one place and replace their values with Pointers, which allows Rust to optimize for duplicate strings. You can verify this optimization by copying the string in the following code n times and then looking at the size of the compiled and packaged executable:

fn main() {
  print("TESTING:12345678901234567890123456789012345678901234567890");
}

fn print(msg: &str) {
  println!("{}", msg);
}
Copy the code

We can probe the generated binaries with the strings command (which is not a feature provided by Rust) :

$ strings target/release/200-prints | grep TESTING
TESTING:12345678901234567890123456789012345678901234567890
Copy the code

String

Strings can be changed to support operations such as intercepting, shrinking, growing, and so on, with additional costs

& STR is converted to String

Briefly, the.to_owned() method changes &str (a reference to a String slice) to an owned String, for example:

let my_real_string = "string literal!".to_owned();
Copy the code

The bottom layer actually looks like this:

String::from_utf8_unchecked(self.as_bytes().to_owned())
Copy the code

Note: self is this in rust

This is why ownership should be understood before learning string. String literal is just a reference without ownership. To become a string with ownership, format transformation is required. Does this mean we should use.to_owned() every time? Yes and no, you can say yes before Traits and generics.

.to_string(),.into(), String::from(), format! (a)

All of these listed in the title can change & STR to String. We’ll explain to those familiar with the above concepts why these methods are not correct

Note that the trait in Rust, which we haven’t touched on yet, can be thought of simply as a mixin pattern in JavaScript.

Why not.to_string()

fn main() {
  let real_string: String = "string literal".to_string();

  needs_a_string("string literal".to_string());
}

fn needs_a_string(argument: String) {}
Copy the code

To_string () converts something to a string, which is often used as part of a Display trait. You’ll see many articles that recommend.to_string() and many others that don’t. The subtle difference in recommendations is the degree to which you want compiler help, and as your programs get big, especially when you start using Generics, you will inevitably need to do type conversions. A variable that was originally &str may be converted to some other type, and if the new value still implements Display then there will be a.to_string() method, and the compiler will have no objection. .to_owned(), on the other hand, makes referenced variables owned, usually by cloning. The compiler raises an error when converting a referenced non-string variable to an owned string. If you feel the difference is acceptable, it doesn’t matter if you use.to_owned() or.to_string()

Note: Clippy will warn you if you use.to_string() on & STR variables

Why not use something. Into ()

fn main() {
  let real_string: String = "string literal".into();

  needs_a_string("string literal".into());
}

fn needs_a_string(argument: String) {}
Copy the code

Something. Into () attempts to convert a variable to the target type by [dest_type]::from(), for example String::from(something). If you want to change &str to String, you’ll get the desired result, but there’s a better way to do it later

Why not use String::from()

fn main() {
  let real_string: String = String::from("string literal");

  needs_a_string(String::from("string literal"));
}

fn needs_a_string(argument: String) {}
Copy the code

String::from(something) is clearer than.into() because you can explicitly declare the type you want, but it’s the same problem as.to_string()

Why not use format! (a)

fn main() {
  let real_string: String = format!("string literal");

  needs_a_string(format!("string literal"));
}

fn needs_a_string(argument: String) {}
Copy the code

format! () is used for formatting, which is the last thing you should use when simply creating a string

Note: Clippy has a special rule Clippy ::useless_format

Implementation details

  • .to_owned():implementation, the callString::from_utf8_unchecked(self.as_bytes().to_owned())
  • String::from():implementation, the call.to_owned()
  • .to_string():implementation, the callString::from()
  • .into():implementation, the callString::from()
  • .format! (a):implementation, the callDisplay::fmt instructions

conclusion

Converting & STR to String is the first half of the String problem. The second part is when you use function arguments, if you want to create a nice API, which do you choose &str or String?

More and more

  • Rust tutorial (1) From NVM to Rust
  • Rust tutorial (2) from NPM to Cargo for the front end
  • Configure Visual Studio Code in Rust (3)
  • Rust tutorial (4) Hello World
  • Rust Tutorial for the front end (5) Borrowing & Ownership
  • Part 1 of Rust Tutorial (6) String
  • Rust Tutorial (7)
  • Rust Tutorial (8)
  • Rust Tutorial (9)
  • Rust tutorial for the front end (10) from Mixins to Traits
  • Rust Tutorial (11) Module for the front-end