2

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?

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • 1
    Relevant book section: [Chapter 18 section 3 on pattern syntax: destructuring to break apart values](https://doc.rust-lang.org/stable/book/ch18-03-pattern-syntax.html#destructuring-to-break-apart-values) – E_net4 Sep 14 '22 at 14:40
  • If the explanation above from the book is yet not enough, please revise the question to incorporate what confuses you. – E_net4 Sep 14 '22 at 14:42
  • See also: https://stackoverflow.com/questions/45782069/is-there-a-way-to-destructure-a-struct-partially and https://stackoverflow.com/q/50962279 – E_net4 Sep 14 '22 at 14:43
  • 1
    You are **not** initializing a struct of a position. You are **destructuring** `self` into `Position(x, y)`, creating the variables `x` and `y` in the process. It basically says "match the thing on the right with the thing on the left, create all given variables in the process and fill them so that both sides match" (disclaimer: very simplified) – Finomnis Sep 14 '22 at 18:08
  • @JazzyJameson Destructuring is a very functional concept, so I'm not surprised that it confused you :) C/C++/Java doesn't have anything comparable. – Finomnis Sep 15 '22 at 12:10
  • @Finomnis Hopefully it will make more sense after this semester where I am learning Haskell. ;) – JazzyJameson Sep 18 '22 at 08:03

1 Answers1

3

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!

pokeyOne
  • 312
  • 1
  • 2
  • 12