3

According to the documentation, we have lvalue and rvalue contexts. How do I know if an expression is in an lvalue context? Is it determined just by the side of the expression in an assignment?

To be more concrete, I need to understand when DerefMut is used for dereferencing and when Deref is used instead? Same with Index and IndexMut.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Roman
  • 31
  • 2
  • The next two paragraphs of the documentation appear to answer your questions. In particular, this: *The left operand of an assignment or compound-assignment expression is an lvalue context, as is the single operand of a unary borrow. The discriminant or subject of a match expression may be an lvalue context, if ref bindings are made, but is otherwise an rvalue context. All other expression contexts are rvalue contexts.* – user4815162342 Feb 18 '17 at 06:59

2 Answers2

9

You are asking two different questions here.

To be more concrete, I need to understand, when DerefMut is used for dereferencing and when Deref is used instead? Same with Index and IndexMut.

This only depends on whether or not the resulting value is used mutably or not. The expressions using the operators belonging to those traits ([] and *) are always lvalue expressions.


Now your more complicated question:

How do I know, if an expression is in an lvalue context? Is it determined just by the side of the expression in an assignment?

Expression categories

To recap: what is an rvalue/lvalue expression?

  • an rvalue expression represents a value
  • an lvalue expression represents a value in a memory location (or in different words: represents a value which lives somewhere/has a home)

Which expressions are lvalue and which are rvalue expressions? There are only a few lvalue expressions:

  • “names” (or rather: paths) which refer to variables (local, function arguments or statics)
  • indexing expressions (e.g. foo[3])
  • deref expressions (e.g. *foo)
  • field access expressions (e.g. foo.bar)

Context categories

What about rvalue/lvalue contexts? These contexts are “slots” inside of expressions. For example, the expression “modulo” (%) has two of those slots, the first and the second operand: ⟨first⟩ % ⟨second⟩. Now these contexts come in two different flavours as well:

  • an rvalue context is a “slot” where a value is expected
  • an lvalue context is a “slot” where a memory location is expected

So which slots are rvalue and which are lvalue contexts? Luckily, the number lvalue contexts is very limited, so here is a full list:

  • the left hand side of a (compound-)assignment (e.g. ⟨lvalue context⟩ = ...; or ⟨lvalue context⟩ += ...;)
  • the operand of an unary borrow (&⟨lvalue context⟩ and &mut ⟨lvalue context⟩)
  • whenever something is bound to a pattern in which a ref appears (e.g. let ref x = &⟨lvalue context⟩;)

Using expression X in context Y

Let's see which expressions we can use in which context:

  • [R in R] rvalue expression in rvalue context: no problem, the expression is used as value (e.g. the literal 3 on the right hand side of an assignment)
  • [L in L] lvalue expression in lvalue context: no problem, the expression is used as memory location (e.g. a variable name on the left hand side of an assignment)
  • [L in R] lvalue expression in rvalue context: since the context requires a value and the expression represents a “value in a memory location” we can just use the value of the expression. So: it's all fine (e.g. a variable name on the right hand side of an assignment). So we can see: lvalue expressions are worth more than rvalue expressions.
  • [R in L] rvalue expression in lvalue context: here is where it becomes problematic. The context requires a memory address, but the expression only represents a value. Sometimes, Rust will do an “rvalue promotion” to make situations like this work. This means that Rust will automatically put the value of the expression into a new memory location (a temporary variable) and use that location in the context. For example, &mut 3 works because of said promotion. This promotion currently doesn't work for assignments, which seems to be a bug in either the documentation or the compiler.
Lymia Aluysia
  • 620
  • 1
  • 6
  • 8
Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
2

DerefMut/Deref/Index/IndexMut are determined by mutability, which is a different question from lvalue/rvalue context. Mutability makes no sense for rvalue context.

In fact, all four traits require the operand self to be in an lvalue context, since they all take &Self or &mut Self as argument.

DerefMut/IndexMut are used whenever mutability is needed, e.g.

*here = ...;
here[i] = ...;

&mut here
&mut here[i]

here.call_some_mut_method(...)
here[i].call_some_mut_method(...)

An lvalue context is where you want to know the address (memory location / reference / ...) of the expression, instead of its value. The place where you need an lvalue.

The left operand of an assignment or compound-assignment expression is an lvalue context, …

here = ...;

here += ...;

… as is the single operand of a unary borrow.

&here

&mut here

let ref ... = here;
// Note: equivalent to `let ... = &here;`

let ref mut ... = here;
// Note: equivalent to `let ... = &mut here;`

Note: This shows an immutable lvalue context

The discriminant or subject of a match expression may be an lvalue context, if ref bindings are made, but is otherwise an rvalue context.

match here {
    Ok(ref ...) => ...,
    Err(ref mut ...) => ...,
}
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005