Summary of some minor problems encountered while writing Rust

发布于:2024-05-09 ⋅ 阅读:(16) ⋅ 点赞:(0)

Automatic dereference (dereference coercion)

struct Car {
    width: u32,
    height: u32,
}

fn main() {
    let mut car1 = Car {
        width: 100,
        height: 200,
    };
    
    let car2 = &mut car1;
    car2.width = 200;
    println!("width: {}", car2.width);

    (*car2).width = 300;
    println!("width: {}", car2.width);
}

car2 is a mutable reference to car1. When we try to change width by car2, Rust will automatically dereferences car2 to get the car1 instance and then modifies the value of width. So car2.width = 200; is equal to (*car2).width = 300;.

But things are not so simple. We can see the following example. The first part is right. But for the second part, if we use y = 10, we will get a error. This is because the automatic deference doesn’t occur on primitive data type. So we must use * explicitly.

let car2 = &mut car1;
car2.width = 200;
println!("width: {}", car2.width);

let mut x = 5;
let y = &mut x;
*y = 10;
println!("y: {}", y);

Why? This may be a design consideration. For struct, it has many elements, so automatic dereference can make code simpler and more readable. But for primitive data tpye, if we don’t use *, it can make people misunderstand that y is reassigned rather than that the value which y refers is changed.

And I need to add some things. * doesn’t get the ownership and just can caculate or other things.

There is another situation. It can work but this isn’t automatic dereference. This depends on + which has Add trait.

let a = 12;
let b = &a;
let c = &a;
println!("{}", b + c);

parse()

It can change the type of the value.

We must specify its type when using parse().

let a: i32 = (&a[1..]).parse().unwrap();

When this value will return, we don’t need to specify its type. Because the function signature is already specifid. But I think we should still do this, which makes code clearer.

collect()

It can take the iterator value and convert them to Vec or other things.

We must specify its type when using collect(). This is because that it can handle not only Vec but also other things like HashMap.

let cells: Vec<_> = first_second.spilt('_').collect();

Using mutable objects continuously

According to borrow rules, we can’t have mutable and immutable borrow at the same time. This value is mutable borrow from self. And self.double_get continue to borrow self. So this is a wrong way, which could lead to the data race.

for (key, value) in self.real_data.iter_mut() {
  *value = self.double_get("get".to_string() + key.as_str());
}

Other example:

match boxes.get_mut(other) {
  Some(v) => {
	parse_command(v, turtle, variables, boxes);
  }
  None => {
	return Err(logo::CommandError::WrongCommandErr(other.to_string()));
  }
}

unwrap() VS except() VS ?

For Result:
unwrap(): If Result is Err(), the program directly panic(); If Result is Ok(), it will return the value in Ok().
except(): It is similar to unwrap(), except that it can panic() custom errors.
?: If Result is Err(), it will return Err(); If Result is Ok(), it will return the value in Ok().

For Option():
unwrap(): If Option is None, the program directly panic(); If Option is Some(), it will return the value in Ok().
except(): It is similar to unwrap(), except that it can panic() custom errors.
?: If Option is None, it will return None; If Option is Some(), it will return the value in Some().

Of course, we can also use macth for more detailed processing.

match VS if let VS let if

let f = match f {
	Ok(file) => file,
	Err(error) => {
		panic!("Problem opening the file: {:?}", error)
	},
};

if let 1 = 1 {
	println!("1");
}

let a = if true {  // This must be a bool value
	1
} else {
	2
};

Why Rust prohibit declaring mutiple mutable reference or mutable and immutable references

There could be many reasons. But I just explain two.
For the data race, suppose you declare one mutable reference and immutable reference. You use mutable reference to change the original data. When you use immutable reference to read the same data, you will find that the data has changed. But this is immutable reference.

For memory security, suppose you declare one mutable reference and immutable reference. If you delete all the data by mutable reference. If you use immutable reference to read the data, they will have the problem.

The mut and &mut in the function signature

fn conmand(mut turtle: Turtle, variables: &mut variables)

iter() VS iter_mut() VS into_iter()

// iter() can get the immutable iterator of a collection.
let vec = vec![1, 2, 3, 4];
for value in vec.iter() {
    println!("{}", value);
}

// iter_mut() can get the mutable iterator of a collection. It applies to change a collection.
let mut vec = vec![1, 2, 3, 4];
for value in vec.iter_mut() {
    *value += 10;
}

// into_iter() can get the ownership.
let vec = vec![1, 2, 3, 4];
let new_vec: Vec<_> = vec.into_iter().map(|x| x * 10).collect();

The struct of the struct

When we use the struct like the following, we must use VariablesCommand::Make{name, value}. That means we can’t use directly a specific value.

pub enum VariablesCommand {
    Make { name: String, value: f32 },
    AddAssign { name: String, value: f32 },
}

the pattern matching of map()

As you can see, x is &&strbut &x is &str. Actually, this &is pattern matching rather than reference.
在这里插入图片描述

the usage of slice

Whether it’s &str or String, you have to use it by & when you use slice.

let a = "abcdedf";
let b = "abcdedf".to_string();
let c = &a[1..];
let d = &b[1..];
println!("c: {}", c);
println!("d: {}", d);