0

I have a struct like below.

struct Test {
    field: Vec<String>,
}

I am passing a reference to this struct as a function argument. I have a second function that takes a &Vec<String>.

fn main() {
    let obj = Test { field: vec![] };
    takes_ref_to_struct(&obj);
}

fn takes_ref_to_struct(obj: &Test) {
    takes_ref_to_field(obj.field);
}

fn takes_ref_to_field(field: &Vec<String>) {}

This usage will not compile due to the error below:

error[E0308]: mismatched types
  --> src/main.rs:11:24
   |
11 |     takes_ref_to_field(obj.field);
   |     ------------------ ^^^^^^^^^ expected `&Vec<String>`, found `Vec<String>`
   |     |
   |     arguments to this function are incorrect
   |
   = note: expected reference `&Vec<String>`
                 found struct `Vec<String>`
note: function defined here
  --> src/main.rs:14:4
   |
14 | fn takes_ref_to_field(field: &Vec<String>) {}
   |    ^^^^^^^^^^^^^^^^^^ -------------------
help: consider borrowing here
   |
11 |     takes_ref_to_field(&obj.field);

Obviously I can just add the suggested borrow, but why is this necessary? If I try to mutate the field

fn takes_ref_to_struct(obj: &Test) {
    obj.field.clear();
}

the compiler complains as expected

error[E0596]: cannot borrow `obj.field` as mutable, as it is behind a `&` reference
  --> src/main.rs:11:5
   |
11 |     obj.field.clear();
   |     ^^^^^^^^^^^^^^^^^ `obj` is a `&` reference, so the data it refers to cannot be borrowed as mutable
linus
  • 35
  • 4
  • 2
    Nitpick: [Take `&[String]`, not `&Vec`](https://stackoverflow.com/questions/40006219/why-is-it-discouraged-to-accept-a-reference-string-vec-or-box-as-a-function). – Chayim Friedman Aug 24 '23 at 08:00
  • I don't understand how the fact that mutating the fields fails relates to the fact that you need to add an ampresand (`&`). – Chayim Friedman Aug 24 '23 at 08:01
  • I clearly do not have ownership of the field. So why does the compiler say, in the first error, that I am passing a `Vec`? Surely it must be a `&Vec`? – linus Aug 24 '23 at 08:04
  • 3
    Because you didn't add a reference, so you're not passing a reference. Sure, that's invalid because you don't have ownership of it, and [the compiler will also tell you that](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=5850024ca3c1830a27b6fab9453a2d9f), it just doesn't in this case because the type mismatch error hides this. – Chayim Friedman Aug 24 '23 at 08:27

2 Answers2

0

Rust is a strictly typed language. While the compiler does perform some transformations automatically, such as with self, in general you're responsible for providing a value of the desired type when you call a function.

The reason the compiler doesn't automatically do this when you have a reference is two-fold. First, if you did own the object, it would be valid to move the field. It would be confusing to automatically add references when the owned object is a reference, but not when it's owned. Regular, reliable behaviour is important in languages for human understanding, ease of tooling, and cross-implementation compatibility. The lack of consistent behaviour has made reliable tooling (e.g., code formatters) in Perl 5 very difficult and multiple implementations effectively impossible.

Second, the compiler doesn't evaluate ownership and borrows until after this stage of the compilation. Type checking happens before borrow checking, so the compiler has not evaluated borrow and ownership rules at this point. It would be very difficult to do borrow and ownership checking until the types are all resolved because the Rust compiler needs to have inferred and computed types to effectively do borrow checking, so practically you need to have the types correct first.

bk2204
  • 64,793
  • 6
  • 84
  • 100
  • So, to clarify, it is possible to have a value which is a `Vec` but is also not owned? Could you explain in what situation that is less confusing than what I would have guessed would happen, which would be to give a reference to the field when the owning object is a reference? Or are you saying that this is not possible due to the stage of compilation that this happens in. In the case of the latter, it seems like I am misunderstanding the distinction between `T` and `&T`. Are they not fundamentally different types? – linus Aug 25 '23 at 01:37
  • Typically, if you have a `Vec`, you own it. If you have a reference, you don't own the thing because it's borrowed. As I said, there are a handful of places where the compiler does automatic conversion between reference and non-reference forms, but in general, it does not and you must do it yourself. – bk2204 Aug 25 '23 at 19:20
  • Note that we don't need borrow checking to append the `&`, we can just see the expected type is a reference (still with type checking). – Chayim Friedman Aug 26 '23 at 18:42
0

I think there is a simpler way to answer OP's question. @Chayim Friedman is correct that you do NOT need to destructure to get multiple mutable references to inner struct fields. That was my mistake. Here's a Rust Playground to test it out.

The dot operator in Rust automatically dereferences, i.e. it will try to use the owned struct underneath. When destructuring, the compiler does NOT deference. Because obj.field uses the dot (.) operator, there is an automatic dereference and it tries to move the inner data instead of assuming you want a reference.

Here is an example of destructuring to get a reference. But with this being said, it's going to be less work to just add the & onto your fields.

Destructuring:

let test = Test { field: vec![] };
let test_ref = &test;
let Test { field: field_ref } = test_ref; // field_ref will be a reference
E_net4
  • 27,810
  • 13
  • 101
  • 139
  • It's nice to disagree, but it's also nice to be _correct_. And in this case you are not. @bk2204 is, even if we can argue about the (dis)ability of the compiler to infer your wish. The compiler doesn't references then dereference, and all the `.` operator does is dereference the operand _before it_, not the field _after it_. Pattern matching giving you a reference when matching against a reference is a whole another feature (called _match ergonomics_), that was added post-1.0 (while the code in the OP has always worked this way). – Chayim Friedman Aug 26 '23 at 18:45
  • And your "fun fact" is also wrong. Why did you decide pattern matching is the "correct" way to get references to multiple fields and field access is "wrong"? Both compile, and always did. – Chayim Friedman Aug 26 '23 at 18:46
  • 1
    When someone is wrong (in an answer) this is _definitely_ a reason to downvote them. In fact, it's probably the most important reason for downvotes on answers. I reversed the downvote, even though to be honest, I don't think this answer is useful now. You're just adding another way to take a reference to a field. Maybe this deserves to be a comment, but this does not answer the question. – Chayim Friedman Aug 29 '23 at 15:13