-1

I wonder, what is the difference between the following three examples regarding the x variable.

use std::collections::HashMap;

#[derive(PartialEq, Eq, Hash)]
enum SomeEnum {
  One,
  Two,
  Three,
}

struct SomeStruct {
}

fn func(hm: &HashMap<SomeEnum, HashMap<SomeEnum, SomeStruct>>) -> Option<SomeStruct> {
  if let Some(x) = hm.get(&SomeEnum::One)?.get(&SomeEnum::Two) {
    //first
  }
  if let Some(x) = hm.get(&SomeEnum::One).unwrap().get(&SomeEnum::Two).as_ref() {
    //second
  }
  if let Some(&x) = hm.get(&SomeEnum::One).unwrap().get(&SomeEnum::Two).as_ref() {
    //third
  }
  None
}

I do not get the difference between these three different ways to get the &SomeStruct. Is it there?

unegare
  • 2,197
  • 1
  • 11
  • 25

1 Answers1

2

The first approach uses the question mark operator ? instead of unwrapping, so if SomeEnum::one is not a key in the outer hash map, it will return None, while the other two approaches will panic and terminate the process instead.

The second approach actually gives you a &&SomeStruct instead of &SomeStruct. I can't see any reason why you'd want to do that.

And finally, the last approach is mostly equivalent to the first one, except for the question mark/unwrap difference. It transforms the Option<&SomeStruct> you get from HashMap::get() to an Option<&&SomeStruct> using as_ref() first, only to drop one level of indirection again by adding the & in the pattern on the left-hand side, so the code is more long-winded and less clear without having any advantage over the first approach.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • As far as I thought, one has to specify on the left side the exact type of the right side in a match statement, so, for instance, to assign a reference one has to write the reference symbol on the left side as well, but it seems that it does not work like that. When `&` should be added, let's say, to drop it and when to match the right side ? – unegare Aug 04 '21 at 12:17
  • And another question, how is it possible to have double reference? what entity is it? P.S. coming from C++ it sounds like a humbug. – unegare Aug 04 '21 at 12:26
  • 1
    A reference is like a pointer in C++, just with additional guarantees. The tyep `&&T` in Rust is comparable to `T **` in C++, i.e. a pointer to a point to `T`. – Sven Marnach Aug 04 '21 at 12:30
  • 1
    On the left hand side, you write a _pattern_ describing what you want to capture. When `Some(x)` is matched against, say, the expression `Some(&&2)`, `x` will end up being `&&2`. If you use `Some(&x)` as the pattern and try to match it against the expression, you see that `x` becomes `&2`. (Using the integer value 2 just for the sake of example here – could be any value of any type.) – Sven Marnach Aug 04 '21 at 12:33
  • I suggest reading the [chapter on pattern matching in the Rust book](https://doc.rust-lang.org/book/ch18-00-patterns.html). – Sven Marnach Aug 04 '21 at 12:34
  • Oh, really, perceiving Rust references as C pointers makes much more sense. What a deceptive `reference` word ... – unegare Aug 04 '21 at 12:35
  • thus, to wrap up, there is no case where one just have to specify the exact type in a whatever match expression, be it `if let` or just `let` either, is there? – unegare Aug 04 '21 at 12:41
  • And one more question :) Am I right that, hence, the `ref` keyword is useful only in a case where a conteiner (Option, Result, Tuple, whatever) containes just values without references? So it comes in handy only when one tries to decompose something which keeps values, not references, only in that case one might want to use the `ref` keyword, doesn't it? – unegare Aug 04 '21 at 12:45
  • 1
    The `ref` keyword basically means that `x` becomes a reference to whatever `ref x` matches to when performing pattern matching. Usually, that's values, but e.g. `let ref x = &2;` would result in `x` being `&&2`. Which is probably pointless, but valid syntax. – Sven Marnach Aug 04 '21 at 12:49
  • Thus, `let ref x = &2;` would result in undefined behaviour (since Rust reference is a C pointer, it would be non-sense, well, `int** x = &&2;` would not be even compiled)? Well, I have checked and it has turned out that `let ref x = &2; println!("{}", **x);` even has printed `2`. I am perplexed. How is it possible? double pointer to a literal value, not to mention just a pointer to a literal value, it already somehow has become familiar. How double pointer to a literal is stored? – unegare Aug 04 '21 at 13:01
  • 1
    The literal implicitly becomes promoted to a static value because its address is taken, and Rust has [lifetime expansion for temporaries in `let` statements](https://stackoverflow.com/questions/47662253/why-is-it-legal-to-borrow-a-temporary). Together, these two features make the code valid. – Sven Marnach Aug 04 '21 at 13:05