Convention for accessing struct members: reference or value?

Hey everybody!

I’ve been working on a little project of mine lately when a question came up which I could not answer myself using a search engine, so maybe we can have a little discussion about the topic.

Let’s assume we have a struct that holds some values, e.g. i32, and we want them to be read- but not writeable. One way would be an impl block with a method named like the member, granting access to it. Here I asked myself: Is there any convention or consensus in the Rust community on whether to return a reference to the member or it’s value, or maybe when to do what?

To clarify, here is a code example.

struct Expample{
    number: i32,
}

impl Expample {
// Wich version is preferrable?
    fn number_by_value(&self) -> i32{
        self.number
    }
    
    fn number_by_reference(&self) -> &i32 {
        &self.number
    }
}

fn main() {
    let example = Expample {number: 23};
    println!{"{}", example.number_by_value()};
    println!{"{}", example.number_by_reference()};
}

i32, i64, f32, f64, etc, are "copy" types (they're copied implicitly, returning self.data automatically copies the value), so both work fine. IMO, the first one returning a value seems better (but I'm very experienced with rust, if I'm wrong tell me). However, if you try doing the first one for a non-copy type (for example, custom struct), the borrow checker will forbid that. For example:

struct Data {
    data: String,
    data2: usize,
}
struct Example {
    data: Data,
}
impl Example {
    fn by_value(&self) -> Data {
        self.data // WILL NOT WORK, BORROW CHECKER
    }
    fn by_reference(&self) -> &Data {
        &self.data // works
    }
}

In this case, the by_reference is the only way that works. Why not change by_value to return self.data.clone(), may you ask? Well, generally, you don't want to force the cloning behavior on whoever uses your function, if they want a clone they can do by_reference().clone(). For copy types like i32, I would return the value.

4 Likes

Thinking in terms of Value vs Reference in Rust leads to fighting with the borrow checker.

The actual choices are:

  1. Copy — when it's cheap and easy to create copies (true for types smaller than a pointer or two)
  2. Owned — if you want to let the caller have their own copy of the value, and use it any way they like as long as they like (but signify that copying is not trivial)
  3. Borrowed — if you want to lock your entire object to be read-only for as long as the reference you've returned still exists.
6 Likes

I might have clarified my question beforehand: I am aware that non copy-types force you to return references or at least spend reasonably much thought on how to return copies of struct members. As you correctly pointed out, the only real choice to make is with small types (like i32 in my example).

What I am asking myself is more if there is a predominant style in returning these types. Following the principle of least surprise, I would like to adhere to a convention if it existed, instead of throwing values at everybody even though the consensus is that references are preferable even with primitives. :wink:
My gut feeling agrees with you, @ndrewxie, that value returns at least look better, though.

Returning by value is more idiomatic, and grants you more flexibility as an API designer. If you change the inner representation later so that one of these values is computed, you'd have to change the reference based fn -- it requires that the i32 be stored in the struct literally.

As the second response pointed out, returning a reference will also prevent your user from doing anything mut with your struct while they hold the result. This can be desirable in some cases, but I'm betting this isn't one of them.

Finally, I'm pretty sure Clippy will complain about returning an i32 by reference. It will certainly complain about passing it by reference.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.