Rust Lang: What is "if let Some(x) = x" doing? - rust

I'm working on a rust project written a couple of years ago, and have come across this piece of code, which is literally:
let mut values = vec![];
for x in maybe_values {
if let Some(x) = x {
values.push(Arc::new(x));
}
}
I understand that "if let" introduces a pattern-matching if (Which seems to be a poor re-use of the keyword "let", but I will get over that - If anyone can help me with a mental mnemonic to make sense of "let" here, please do!).
But what is the test Some(x) = x doing?
From my testing, it seems to be a trick/idiom to both a) test that the loop variant 'x' is Some(), and also b) end up with the unwrap()ped value in x.
But I can't fully explain it to myself, and can't find reference to this being an idiom anywhere.
Hope you can help my Rust education path. Thanks.

This is a shorthand for using a full match statement when you only care about matching a single use case.
So this block of code:
if let x = y {
foo();
} else {
bar();
}
Is equivalent to using a full match:
match y {
x => {
foo();
}
_ => {
bar();
}
}
For your specific case, it is equivalent to this. The inner x uses the same name as the outer variable which can be confusing, but they are two separate values.
let mut values = vec![];
for x in maybe_values {
match x {
Some(y) => values.push(Arc::new(y)),
_ => {},
}
}

There are two completely different variables in play here. It's equivalent to.
let mut values = vec![];
for x_1 in maybe_values {
if let Some(x_2) = x_1 {
values.push(Arc::new(x_2));
}
}
In Rust, the right-hand side of a let is evaluated with the left-hand variable not in scope, so when the if let is evaluated, the outer x is still in-scope. Then, if it's a Some value, we make a new variable x which contains the inside of the Option. This variable shadows the previous x, making it inaccessible inside the if statement (in the same way that a function argument called x would render a global variable named x inaccessible by shadowing).

Related

What does "let MyStruct(x, y) = self;" do? And why can I access x and y?

I was doing an exercise on exercism with Rust and came across a community solution where the implementation of the given user confused me.
pub struct Position(pub i16, pub i16);
impl Position {
pub fn manhattan(&self) -> i16 {
let Position(x, y) = self;
x.abs() + y.abs()
}
}
What I do not understand is:
let Position(x, y) = self;
x.abs() + y.abs()
As I understand it, I am initializing a struct of Position, but how am I initializing it without setting it equal to a variable? Further more, how am I then able to suddenly have access to the arguments?
What you see here is called destructuring, and is actually creating variables x and y from self.
In this case, if you were to take this line of code:
let Position(x, y) = self;
What this is actually saying is equilalent to this:
let x = &self.0;
let y = &self.1;
You can think of the Position(x, y) part of the assignment as a pattern. You will see pattern matching in many places in Rust, most commonly in match statements. For example, unwrapping a variable of type Option<String> might look like:
match some_optional_string {
Some(value) => println!("Got value: {value}"),
None => println!("Got no value")
};
Pattern matching also appears in conditions as well.
if let Some(value) = some_optional_string {
println!("Got value: {value}");
}
Another way to think of the particular example that you are referring to, is to replace self with its actual value. Say for example self was initialized with the values 3 and 4, this might look like so:
let some_position = Position(3, 4);
Now if you were to call some_position.manhatten(), self is equivalent to a &Position(3, 4)
If you replace all uses of self in the function with the value:
let Position(x, y) = &Position(3, 4);
x.abs() + y.abs()
(This won't actually compile because of lifetimes, but this is beside the point)
You can see more clearly now that Position(x, y) matches &Position(3, 4) but assigning x and y to &3 and &4
I saw you were also learning Haskell, which has a very similar concept. In haskell you could have a function that took in a Maybe value, and have different definitions based on which it matched.
someFunc :: Maybe String -> String
someFunc (Just value) = value
someFunc Nothing = "I'm a different string"
Which has the same concept of destructuring or matching.
Hopefully this rambling makes sense!

What is & doing in a rust for in loop? [duplicate]

This question already has an answer here:
What is the difference between `e1` and `&e2` when used as the for-loop variable?
(1 answer)
Closed 1 year ago.
Trying to understand how & works in a rust for..in loop...
For example let's say we have something simple like a find largest value function which takes a slice of i32's and returns the largest value.
fn largest(list: &[i32]) -> i32 {
let mut largest = list[0];
for item in list {
if *item > largest {
largest = *item;
}
}
largest
}
In the scenario given above item will be an &i32 which makes sense to me. We borrow a slice of i32's and as a result the item would also be a reference to the individual item in the slice. At this point we can dereference the value of item with * which is what I assume how a pointer based language would work.
But now if we alter this slightly below...
fn largest(list: &[i32]) -> i32 {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
If we put an & in front of item this changes item within the for..in into an i32... Why? In my mind this is completely counterintuitive to how I would have imagined it to work. This to me says, "Give me an address/reference to item"... Which in itself would already be a reference. So then how does item get dereferenced? Is this just a quirk with rust or am I fundamentally missing something here.
All variable assignments in Rust, including loop variables in for loops and function arguments, are assigned using pattern matching. The value that is being assigned is matched against the target pattern, and Rust tries to fill in the "blanks", i.e. the target variable names, in a way that substituting the values makes the pattern match the value. Let's look at a few examples.
let x = 5;
This is the simplest case. Obvious, substituting x with 5 makes both sides match.
if let Some(x) = Some(5) {}
Here, x will also become 5, since substituting that value into the pattern will make both side identical.
let &x = &5;
Again, the two sides match when setting x to 5.
if let (Some(&x), &Some(y)) = (Some(&5), &Some(6)) {}
This assignment results in x = 5 and y = 6, since substituting these values into the pattern makes both sides match.
Let's apply this to your for loop. In each loop iteration, the pattern after for is matched against the next value returned by the iterator. We are iterating an &[i32], and the item type of the resulting iterator is &i32, so the iterator yields a &i32 in each iteration. This reference is matched against the pattern &item. Applying what we have seen above, this means item becomes an i32.
Note that assigning a value of a type that does not have the Copy marker trait will move that value into the new variable. All examples above use integers, which are Copy, so the value is copied instead.
There is no magic here, pure logic. Consider this example:
let a = 1;
let b = &a; // b is a reference to a
let &c = &a; // c is a copy of value a
You can read the third line of the example above as "Assign reference to a to a reference to c". This basically creates a virtual variable "reference to c", assigns to it the value &a and then dereferences it to get the value of c.
let a = 1;
let ref_c = &a;
let c = *ref_c;
// If you try to go backwards into this assignments, you get:
let &c = &a;
let &(*ref_c) = &a;
let ref_c = &a; // which is exactly what it was
The same occurs with the for .. in syntax. You iterate over item_ref, but assign them to &item, which means that the type of item is Item.
for item_ref in list {
let item = *item_ref;
...
}
// we see that item_ref == &item, so above is same as
for &item in list {
...
}

Why is the compiler letting me borrow another borrow in a box?

I'm wondering if someone can help me understand why this program behaves as it does:
fn main() {
let mut x = 456;
{
let mut y = Box::new(&x);
y = Box::new(&mut y);
println!("GOT {}",*y);
}
}
This program compiles under rust 1.35.0 (both 2015 and 2018 editions), and prints
GOT 456
But, I'm confused what's going on here. I'm guessing that this is an example of an auto-dereference. So, in reality, it looks like this:
fn main() {
let mut x = 456;
{
let mut y = Box::new(&x);
y = Box::new(&mut *y);
println!("GOT {}",*y);
}
}
Is that it?
This is a case of deref coercion, but one that is obfuscated by a few other unnecessary parts of the code. The following improvements should be made here:
The mut modifier on variable x is not needed because it is never modified.
The borrow of y in Box::new(&mut y) does not have to be mutable because the variable holds an immutable reference.
The println! implementation also knows to print values behind references, so the explicit * is not needed.
Then, we get the following code:
fn main() {
let x = 456;
{
let mut y = Box::new(&x);
y = Box::new(&y);
println!("GOT {}", y);
}
}
y is a variable of type Box<&i32> which is initially bound to a box created in the outer scope. The subsequent assignment to a new box works because the &y, of type &Box<&i32>, is coerced to &&i32, which can then be put in the box by automatically dereferencing the first borrow. This coercion is required because the variable x can only be assigned values of the same Box<&i32> type.
The lifetime of the reference inside both boxes also ended up being the same, because they refer to the same value in x.
See also:
What is the relation between auto-dereferencing and deref coercion?

Confused about ref keyword usage in closures

I'm trying to understand how & and ref correspond. Here's an example where I thought these were equivalent, but one works and the other doesn't:
fn main() {
let t = "
aoeu
aoeu
aoeu
a";
let ls = t.lines();
dbg!(ls.clone().map(|l| &l[..]).collect::<Vec<&str>>().join("\n")); // works
dbg!(ls.clone().map(|ref l| l[..]).collect::<Vec<&str>>().join("\n")); // doesn't work
dbg!(ls.clone().map(|ref l| &l[..]).collect::<Vec<&str>>().join("\n")); // works again!
}
From the docs:
// A `ref` borrow on the left side of an assignment is equivalent to
// an `&` borrow on the right side.
let ref ref_c1 = c;
let ref_c2 = &c;
println!("ref_c1 equals ref_c2: {}", *ref_c1 == *ref_c2);
What would the equivalent to |l| &l[..] be with |ref l|? How does it correspond to the assignment examples in the docs?
Taking a look at the docs page for Lines(The iterator adapter for producing lines from a str), we can see that the item produced by it is:
type Item = &'a str;
Therefore the following happens when trying to do the "doesn't work" version:
dbg!(ls.clone().map(|ref l| l[..]).collect::<Vec<&str>>().join("\n")); # doesn't work
//Can become:
let temp = ls
.clone()
.map(|ref l| l[..])
.collect::<Vec<&str>>()
.join("\n");
println!("{}", temp);
Here we can see a crucial problem. l if of type &&str (Which I will explain below) so indexing into it will create a str, which is unsized and therefore cannot be outside of a pointer of some sort.
Now, onto the real thing you wanted to learn: What a ref pattern does:
When doing pattern matching or destructuring via the let binding, the ref keyword can be used to take references to the fields of a struct/tuple.
What this does is the following:
When we have let ref x = y, we take a reference to y.
When pattern matching on something (Like in your closure arguments you showed) we have a slightly different effect: the value under the reference is moved into the scope and then taken reference to while exposing a way to take the value under the reference. For example:
fn foo(ref x: String) {}
let y: fn(String) = foo;
This works because what is essentially being done is this:
fn foo(x: String) {
let x: &String = &x;
}
So what ref x does is take ownership of x and produce a reference to it.
On the other hand
When we have let &x = y we move a value out of y.
This is the opposite of ref, in that we take ownership of the value under y if we can. For example:
let x = 2;
let y = &x;
let &z = y; //Ok, we're moving a `Copy` type
This is only ok for copy types though as though this isn't exactly the same as let x = *y which would work for owned Boxes.

Is it possible to specify the lifetime on match variables in rust?

For example:
enum FooBar {
Bar(Vec<int>),
Other
}
fn main() {
let default:&[int] = [];
let x = Bar(Vec::from_slice([1, 2, 3]));
let y = match(x) {
Bar(z) => z.slice(0, z.len()),
_ => default
};
println!("{}", y);
}
In this example the slice is not valid because of the lifetime on z; but we know that the actual data here (x) is valid for the entire block.
Is there some 'a syntax you can use here to help hint this to compiler? If so, how do you do it?
(I believe I've seen some kind of 'a match { } or match x 'a { } / something syntax in the past, but I can't find any examples now that I'm looking)
Sure, there is some syntax, though it is not as explicit on lifetimes as you mean and in fact it is more general. By doing match as you do you are actually moving x into the match, so it is consumed. No wonder that z.slice(...) can't escape the block. But you can avoid moving x into the match by using reference patterns (note the ref before z):
let default: &[int] = [];
let x = Bar(vec![1, 2, 3]);
let y = match x {
Bar(ref z) => z.slice(0, z.len()),
Other => default
};
println!("{}", y);
This way x is not moved into match block - its internals are borrowed instead. This program compiles and prints out [1, 2, 3].

Resources