Enum-valued parameters: copy or borrow?

For parameters whose types are enums: Is it better to copy or to borrow? Or does it depend on the data that is wrapped by the enum values?

fn copy_param(param: Option<String>) {}
fn borrow_param(param: &Option<String>) {}

Related – which is better?

fn copy_param(param: Arc<String>) {}
fn borrow_param(param: &Arc<String>) {}

I’m a beginner, so take this not as gospel and with a teaspoon of grains of salt.
As with other Copy values, it seems preferable to take the least indirection possible.
For the second one, do you need the double indirection? Arc is already a smart pointer.

1 Like

Right. It comes down to “cost of copying” vs. “cost of following the indirection”.

Copy means that Clone is just memcpy so it’ll be incredibly fast. In the end, the pointer you’re passing by borrowing will also need to be copied. I think that thinking about the cost here rather than the clarity is premature optimization.

1 Like

My reply below will mostly be about Option.

Note that Option<String> is by move NOT by copy, since String isn't Copy and thus Option<String> isn't Copy either. So there's not much to do with enums going on with it, but about move-vs-borrow in general.

The rules for trivial field-less enums (like cmp::Ordering) are very different from anything involving String.

Note that &Option<String> is a code smell for a parameter. Is there a reason that you need the caller to have exactly an Option<String> variable somewhere that you're borrowing?

In general, it's more flexible for parameters to be Option<&T> instead of &Option<T>, since then people can just pass None.

And that's even more true for Option<String>, since the parameter can be Option<&str> instead -- that way I can pass Some(&blah[10..20]) to it. If that function took &Option<String>, I'd instead have to copy those 10 bytes into a fresh String, then put that String into an Option, and then pass a reference to that.

(For return types you can consider returning &Option<T>, since people can always all .as_ref() to turn that into Option<&T>, but there's no free way to go the other way.)

4 Likes

Probably the former. &Arc<T> is double indirection. Arc<T> is already a pointer.

1 Like

Prefer Arc<T> over &Arc<T> only if you are expressing some kind of ownership transfer semantic.

Cloning an Arc<T> has a non-trivial cost, unless you are sure the function need a owned Arc<T>, pass an &Arc<T> instead.

2 Likes

That's a good way to put it. As with all tradeoffs, "it depends".

Note that this is a specific instance of the more general rule,

If you always need an owned version of something, make your caller give you one (as opposed to cloneing or to_owneding inside the function)

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.