Rust By Example 8.5.1.4 match Destructing pointers/ref. Dereferncing with * not needed?

Hello

In the mentioned chapter, rust book 8.5.1.4, the comment claims implicitly that the * is needed to avoid the & before val.

    // To avoid the `&`, you dereference before matching.
    match *reference {
        val => println!("Got a value via dereferencing: {:?}", val),
    }

Output:

Got a value via dereferencing: 4

I find that I get the same output with (no * in front of "reference" and no & in front of "val"):

    match reference {
        val => println!("Got a value via dereferencing: {:?}", val),
    }

Tried replacing {:?}with {} to ensure I am not cheated by debug interpretation somehow, but that produced the same "correct" output.
My compiler version:

$ rustc --version
rustc 1.66.0 (69f9c33d7 2022-12-12) (Arch Linux rust 1:1.66.0-1)

I am confused by what // To avoid the `&`, you de-reference before matching. tries to tell me and therefore what is going on in the code. My assumption is that the missing * in front of "reference" and there not being an & in front of "val" would print the address at which the integer 4 is stored, not the integer itself? I would be shocked to find that the address this integer is stored at is by chance 4, so i changed the number to 3. Here is the complete code with all modificaitons, producing 2 lines of identical output:

fn main() {
    let reference = &3;

    match reference {
        &val => println!("Got a value via destructuring: {:?}", val),
    }

    match reference {
        val => println!("Got a value via dereferencing: {}", val),
    }
}

Rust playground behaves the same.

IIUC the behavior of match changed in 2018. Before this you had to put a ref or ref mut when you were matching a reference, but now when the thing you're matching is a reference the compiler does this for you.

Edit to add more detail.

You can specifically not take ownership of a type using the ref keyword when pattern-matching, e.g.

if let Some(ref inner) = some_option { .. }

lets you borrow rather than move the insides of the option. But after 2018, the compiler automatically borrows the inner content when the outer content is borrowed:

let opt_ref = &some_option;
// The compiler will automatically take a reference of the contents of the option
if let Some(inner) = opt_ref { .. }

Thank you @derekdreery

That answers my question even if I am not able to comprehend the more detailed explanation (coming form embedded C).

What would I have to do if I actually wanted the address of the integer? I must admit, this level of compiler smartness does make me feel somewhat uneasy, given my background. This automation removes an otherwise obvious mechanic (getting the address of a pointer)...?

Here’s what’s going on

fn main() {
    let reference = &3;

    println!("Printing the reference: {}", reference) // -> prints “3”
}

Match ergonomics don’t play any role in these examples.

5 Likes

There’s a few formatting options one of which is to print addresses of pointers.

fn main() {
    let reference = &3;

    println!("Printing the reference: {:p}", reference) // -> prints the address, something akin to “0x56368e08d05c”
}

Assuming you’re talking about the fact that the value is printed, not the address: There’s no compiler smartness involved. It merely uses the trait implementation of Display or Debug (the latter for {:?}) for references in the standard library.

4 Likes

@steffahn is right, but in case my explanation of match semantics is useful, I'll carry on.

To get the reference as a pointer, you can simply cast it: let p = my_ref as *const u8 (or whatever pointer type you are using). Here's an example of how this works.

@steffahn so am I right in assuming that println will use this impl to format references?

1 Like

Yes. I had actually added the same link[1] to my comment above in the meantime.


  1. with some future-proofing by inserting the version 1.66.0 ↩︎

2 Likes

Thank you @steffahn and @derekdreery

Coming form C, having to use {:p} does seem a bit odd. Does rust support pointer arithmetic?

Running this in the playground seems to indicate that it would depend if the trait pointer is implemented:

fn main() {
    let reference = &3;
    println!("Adress: {:p}, address + 10: {:p}", reference, reference + 10);
}

Part of the error message:

the trait `Pointer` is not implemented for `{integer}`

Pointer arithmetic is very tricky in Rust, because of the rules of provenance for pointers and references. But normally this is not necessary - you can allocate an array rather than a block of memory and the compiler takes care of doing this correctly and 'safely'. The Nomicron provides information about this. In regular programming this almost never comes up, but when you're interacting with MMIO for example it can do, but in this case you can still cast the pointer to a reference to some structure like an array and work with that. Check out the peripheral access crates (pac crates like GitHub - nrf-rs/nrf-pacs: Peripheral Access Crates (PACs) for nRF microcontrollers) - these do the work for you and are auto-generated based on SVD files that you get with ARM chips.

Sometimes it can be hard to make Rust's rules line up exactly with very low level programming, but in general it is possible, and thanks to the crate ecosystem & working groups often you don't have to do it yourself.

The fmt::Pointer trait means that a value can be displayed as an address. The trait is implemented for references, smart pointers, and raw pointers. Pointer arithmetic is supported only on raw pointers, not on references:

fn main() {
    let reference = &3;
    let plus10 = (reference as *const i32).wrapping_add(10);
    println!("Adress: {:p}, address + 10: {:p}", reference, plus10);
}

But pointer arithmetic really shouldn't be used in safe code.

Thank you very much @derekdreery and @LegionMammal978

Great information!

Please correct me if my key takeaways are not correct:

  • Passing & or just to the implementation of the display trait is of no difference.
  • For matching with match, this is does not make a difference either.
  • I can still get the value of a reference by casting it, but this is not safe and the work may have been done elsewhere. (I am still to fresh with rust to have been in significant contact with the crate system. For now I imagine is as library such as those found python).

Mostly right. Except for the second point: it does make a difference for match, though this difference is somewhat hidden in the code at hand since of val becomes a &i32, the println still works. But e. g. let n: i32 = val; inside the match are would not work when val becomes &i32 and you'd need to dereference to let n: i32 = *val;. For more complicated patterns like structs and enums it also makes a difference and then the “match ergonomics” feature / rules can come into play, or you could decide to change it and match against a reference pattern.

Hello @steffahn

Looked at the code again and it seems more clear now, val simply becomes what the argument to match is, there are no restrictions.

After several times reading your would-not-work example, I think i got that too. The syntax is really something I still have to get used to. In my own words, assigning val to n, if val is assumed to be i32, would not work, because it's type is a pointer to i32. *val dereferences the pointer, so that *val becomes the i32 to which val points?
val is an &i32
*val is a i32

The book example modifies reference and overall we get this:
val becomes *reference which is *&4 which simplifies to 4:
go_to_address(give_address_of(4)).

That leaves me with the first part of the example:

match reference {
        // If `reference` is pattern matched against `&val`, it results
        // in a comparison like:
        // `&i32`
        // `&val`
        // ^ We see that if the matching `&`s are dropped, then the `i32`
        // should be assigned to `val`.
        &val => println!("Got a value via destructuring: {:?}", val),
    }

I gather this:
&val does NOT become &reference and then &&i32 (i.e. a pointer to a pointer), instead:
The whole expression &val becomes reference and then the destruction takes place as explained by the comment, as though it was an awk regex: /&(*)/
& var
& 4
^ |4 Grapped into var
^-Matched
There is some implicit dereferencing going on and I assume this is a special case for this purpose. The explanation given by the comment, and myself, surely are a help to grasp and memorize the behavior rather then the existence of a generic mechanic?

If I actually wanted a pointer to a pointer, I would need to do this:

match reference {
        val => println!("Got a value via destructuring: {:?}", &val),
    }

Ignoring what the display/Debug traits would do with it, a pointer to a pointer is now passed?

1 Like

What you're encountering here is the duality of patterns vs. expressions. It's not so special if you think about it and compare with other instances of pattern matching e. g. to destructure tuples or match an Option. For a slightly longer piece of information with code examples, feel free to read: Patterns Are Not Expressions by @H2CO3 :slight_smile:


A pattern can also indroduce another level of reference. The syntax for that is ref …. See e. g. here: The ref pattern - Rust By Example

1 Like

Patterns Are Not Expressions tremendously clears things up The ref pattern - Rust By Example follows perfectly up with examples, thank your @steffahn.

This was a surprising and invaluable excursion, the help by everyone is greatly appreciated and I hope others find value in this too, if they are puzzled by the same comment. I corrected the thread title "Rust By Example" rather than "Rust Book".