24 days from Node.js to Rust

preface

Moving from JavaScript arrays and loops to Rust’s counterpart requires a bit of understanding of new concepts and data types. In some ways Rust is more concise than JavaScript, in others it is more than ten times more concise

We’ve covered Vec and VecDeque in Tutorial 7, but one of the difficulties that comes with iterating is that many of the array methods found in JavaScript also exist in Rust, but are encapsulated by a ten-fold Iterator data structure

The body of the

vec! [], Vec, VecDeque

An array in Rust must be initialized with all its members initialized and of a fixed length. You can change the value of each member, but not the length of the array. For example, this code is not allowed:

let mut numbers = [1.2.3.4.5];
numbers.push(7); // no method named `push` found for array `[{integer}; 5]`
println!("{:? }", numbers);
Copy the code

If you want to change the length dynamically, you need to use Vec and VecDeque. Vec can add and subtract content in the tail, and VecDeque can add and subtract content in the head and tail. Using the vec! [] macro we can easily create Vec:

let mut numbers = vec![1.2.3.4.5];  // ⬅ note vec! The macro
numbers.push(7);
println!("{:? }", numbers);
Copy the code

cycle

For (… ; … ; …).

Rust does not support for (… ; … ; …). Form of circulation, Rust uses for… The IN expression is combined with the range operator to implement the loop

The JavaScript version:

let max = 4;
for (let i = 0; i < max; i++) {
  console.log(i);
}
Copy the code

The Rust version:

let max = 4;
for i in 0..max {
  println!("{}", i);
}
Copy the code

Results:

1
2
3
Copy the code

The for… in

There are no JavaScript objects in Rust for… In cannot be used to iterate over an object’s key. Rust has a HashMap (see Tutorial 8), but you can use the.keys() method if you want to iterate

Note: the.keys() method traverses key values in a variable order. Do not rely on the order

The TypeScript version:

let obj: any = {
  key1: "value1".key2: "value2"};for (let prop in obj) {
  console.log(`${prop}: ${obj[prop]}`);
}
Copy the code

The Rust version:

let obj = HashMap::from([
  ("key1"."value1"),
  ("key2"."value2")]);for prop in obj.keys() {
  println!("{}: {}", prop, obj.get(prop).unwrap());
}
Copy the code

Results:

key1: value1
key2: value2
Copy the code

The for… of

In JavaScript for… Of and Rust for… In is kind of a one-to-one correspondence

The TypeScript version:

let numbers = [1.2.3.4.5];
for (let number of numbers) {
  console.log(number);
}
Copy the code

The Rust version:

let numbers = [1.2.3.4.5];
for number in numbers {
  println!("{}", number);
}
Copy the code

Results:

One, two, three, four, fiveCopy the code

while (! done)

In JavaScript we use while (! Done), the counterpart in Rust is while let. The following code continues as long as.dowork () returns Some() (you can try Result, Ok, or Some other type)

struct Worker {
  data: VecThe < &'static str>,}impl Worker {
  fn doWork(&mut self) - >OptionThe < &'static str> {
    self.data.pop()
  }
}
let mut obj = Worker {
  data: vec!["a"."b"."c"]};while let Some(data) = obj.doWork() {
  println!("{}", data);
}
Copy the code

Results:

c
b
a
Copy the code

The do… while

Rust does not do… While, you can use a loop expression instead

While (true)…

The loop expression used in Rust is more intuitive and simple:

let mut n = 0;
loop {
  n += 1;
  if n > 3 {
    break; }}println!("Finished. n={}", n);
Copy the code

Results:

Finished. n=4
Copy the code

Labels, break, continue

Labels in Rust work in a similar way to JavaScript, except that Rust adds a stop prefix

The TypeScript version:

outer: while (true) {
  while (true) {
    breakouter; }}Copy the code

The Rust version:

'outer: loop {
  loop {
    break 'outer; }}Copy the code

break & loop

Loop expressions can have a return value, which is much better than setting the initial value outside the loop and updating it inside the loop

let value = loop {
  if true {
    break "A";
  } else {
    break "B"; }};println!("Loop value is: {}", value);
Copy the code

Results:

Loop value is: A
Copy the code

The iterator

Rust uses iterators to process sequences, and iterators can rely on chains to generate more iterators. Unlike the iterator methods in JavaScript, Rust iterators are lazy and are executed only when called

All iterators implement the Iterator trait, which makes the interface of each Iterator similar. Unlike some other foundational traits, the trait has an association type called Item as a placeholder for iterating objects

Association types in Traits are similar to generics in that they are placeholders for a type. See more in The Rust Book, CH 19.03: Advanced Traits

usage

Vec is not an iterator, we need to call a method to create the iterator, and since the iterator is lazy, we have to call the method to get the value, so we call the method twice:

let list = vec![1.2.3];
let doubled: Vec<_> = list
  .iter()
  .map(|num| num * 2)
  .collect();
println!("{:? }", doubled);
Copy the code

In many data structures.iter() can return an iterator, which will be consumed by the iterator’s.collect() method

We can call the.next() method directly to get the iterator value

Annotations You may encounter an error when using.collect() : error[E0282]: Type Annotations needed

let list = vec![1.2.3];
let doubled = list.iter().map(|num| num * 2).collect();
Copy the code

Error:

error[E0282]: type annotations needed
  --> crates/day-17/iterators/src/main.rs:13:7
   |
13 |   let doubled = list.iter().map(|num| num * > 2).collect();
   |       ^^^^^^^ consider giving `doubled` a type

For more information about this error, try `rustc --> explain E0282`.
Copy the code

At first you may not know why you need to specify the type. Rust knows the type of data in the map and the type to be returned, so why do we need to specify the type as follows?

let list = vec! [1, 2, 3]. let doubled: Vec\<i32>= list.iter().map(|num| num * 2).collect();Copy the code

Rust does know the element type I32, but not the Vec<> type. When Rust knows its specific type, you can omit the type with _, for example, Vec<_>

.iter() returns an immutable reference to the element. If you want to change the element, use.iter_mut() instead

.filter()

The iterator’s.filter() method returns a new iterator:

The TypeScript version:

let numbers = [1.2.3.4.5];
let even = numbers.filter((x) = > x % 2= = =0);
console.log(even);
Copy the code

The Rust version:

t numbers = [1.2.3.4.5];
let even: Vec<_> = numbers.iter().filter(|x| *x % 2= =0).collect();
println!("{:? }", even);
Copy the code

Results:

[2, 4]
Copy the code

Note the * before the x variable in the code above, because.filter() references a layer, and most iterators also reference a layer, and we need to dereference it if we want to get a reference

.find()

.find(predicate) is essentially a.filter(predicate).next(), which will consume iterations until the predicate is satisfied and returns the specified value

The JavaScript version:

et numbers = [1.2.3.4.5];
let firstEven = numbers.find((x) = > x % 2= = =0);
console.log(firstEven);
Copy the code

The Rust version:

let numbers = [1.2.3.4.5];
let first_even = numbers.iter().find(|x| *x % 2= =0);
println!("{:? }", first_even.unwrap());
Copy the code

Results:

2
Copy the code

.find() can be called multiple times and is mnemonic, but cannot be done in JavaScript

let numbers = [1.2.3.4.5];
let mut iter = numbers.iter(); // Note, our iter is mut
let first_even = iter.find(|x| *x % 2= =0);
println!("{:? }", first_even.unwrap());
let second_even = iter.find(|x| *x % 2= =0);
println!("{:? }", second_even.unwrap());
Copy the code
2, 4Copy the code

.forEach()

.for_each() immediately consumes iterators, which you can use at the end of the iterator chain to manipulate each element (using plain loops is usually a more readable option).

The JavaScript version:

et numbers = [1.2.3];
numbers.forEach((x) = > console.log(x));
Copy the code

The Rust version:

let numbers = [1.2.3];
numbers.iter().for_each(|x| println!("{}", x));
Copy the code

Results:

1
2
3
Copy the code

.join()

Join () can be used on arrays and Vec, cutting does not require iterators

The JavaScript version:

let names = ["Sam"."Janet"."Hunter"];
let csv = names.join(",");
console.log(csv);
Copy the code

The Rust version:

let names = ["Sam"."Janet"."Hunter"];
let csv = names.join(",");
println!("{}", csv);
Copy the code

Results:

Sam, Janet, Hunter
Copy the code

.map()

.map() returns a new iterator

The JavaScript version:

let list = [1.2.3];
let doubled = list.map((x) = > x * 2);
console.log(doubled);
Copy the code

The Rust version:

let list = vec![1.2.3];
let doubled: Vec<_> = list.iter().map(|num| num * 2).collect();
println!("{:? }", doubled)
Copy the code

Results:

[2, 4, 6]
Copy the code

How do I return an iterator

Using.collect() to return specific data structures is a poor form when the iterator itself can be returned. Returning iterators preserves flexibility and the lazy evaluation expected by Rust programmers. Since the basic Iterator is a trait, we can return it just as we returned closures and other values in the previous tutorial

The following example returns an Iterator instead of Vec< &string >

struct Names {
  names: Vec<String>,}impl Names {
  fn search<T: AsRef<str> > (&self, re: T) -> impl Iterator<Item = &String> {
    let regex = regex::Regex::new(re.as_ref()).unwrap();
    self.names.iter().filter(move |name| regex.is_match(name))
  }
}
Copy the code

If you are confused by AsRef< STR >, re-read tutorial 12

reading

  • The Rust Book: ch 13.02 – Iterators
  • Rust by Example: Flow control
  • Rust by Example: Vectors
  • Rust by Example: Iterators
  • Rust docs: Vec
  • Rust docs: VecDeque
  • Rust docs: Iterator

conclusion

It is important to properly transform mental models of how iterators and lists work. If you are familiar with the functional programming style of JavaScript, it is beneficial to learn Rust, even though Rust is not a purely functional programming language