I don’t feel like this gave a satisfactory answer the question. Since everything was inlined, the argument passing convention made no difference in the micro benchmarks. But what happens when it does not inline? Then you would actually be testing by-borrow be by-copy instead of how good rust is at optimizing.
In Rust it's considered idiomatic to pass things by-value whenever you can. Usually this is also the most performant option, since it avoids dereferencing in the callee. Of course, if your struct is truly enormous, you may want to break this rule to avoid large copies. But in that case you probably want to Box the struct anyway. Of course, if your struct contains something that can't be copied--like a Vec --you'll ha…
Should small Rust structs be passed by-copy or by-borrow? (2019)
61–70 of 238 posts
Re: Should small Rust structs be passed by-copy or by-borrow? (2019)
#62Oh neat, that’s my blog. My old posts don’t resurface on HN that often. Lots of criticism of my methodology in the comments here. That’s fine. That post was more of a self nerd snipe that went way deeper than I expected. I hoped that my post would lead to a more definitive answer from some actual experts in the field. Unfortunately that never happened, afaik. Bummer.
Re: Should small Rust structs be passed by-copy or by-borrow? (2019)
#63I don’t feel like this gave a satisfactory answer the question. Since everything was inlined, the argument passing convention made no difference in the micro benchmarks. But what happens when it does not inline? Then you would actually be testing by-borrow be by-copy instead of how good rust is at optimizing.
I sort of agree and sort of disagree. > Then you would actually be testing by-borrow be by-copy instead of how good rust is at optimizing. I don’t think the question is actually: “what is faster in practice, a by-copy method call or a by-value method call”, I think the question is: “as an implementer, which semantics should I choose when I’m writing my function”. For the second question: “Rust is usually pretty good…
The root question is indeed “what semantics should I use”. And the answer I came up with was “the compiler does a lot of magic so by-copy seems pretty good”. I agree with the previous commenter this is not a satisfying conclusion!
My experience with Rust is that it requires a moderate amount of trust in the compiler. Iterator code is another example where the compiler should produce near optimal code. Emphasis on should!
Re: Should small Rust structs be passed by-copy or by-borrow? (2019)
#64This is one advantage of Ada, where parameters are abstractly declared as "in" or "in out" or "out". The compiler can then decide how to best implement it for that specific size and architecture.
Re: Should small Rust structs be passed by-copy or by-borrow? (2019)
#65Re: Should small Rust structs be passed by-copy or by-borrow? (2019)
#66This is one of the problems I have with writing rust code. You have to think about so many mundane details that you barely have time left to think about more important and more interesting things.
Rust uses syntax that feels familiar but means completely different things than in pretty much any other language.
For example '=' doesn't mean assign handle or copy. It by default means move.
'let' doesn't mean create a name for something. It means create physical space for something (of known size) that can be moved into or moved out of.
You don't deal with objects and values of primitive types. Instead everything in Rust is a value. When you move, you move the value. If you compare, you compare by value. If you pass something from variable into function, you move the value into the function.
And when the space where you keep the value goes out of scope value dies with it if it wasn't moved out to somewhere else.
Scope for variables (which are just named spaces for values) ends with the end of the block, but some values, created by functions and returned from them, if they are not moved into any named space, can die sooner, even in the middle of the line where they were acquired from function call.
Everything else stems from that fixed size moved value semantics. If you don't want to move the value into the function when you call it you need to pass something else instead, so you create and pass in the borrow. But you have to ensure that the value doesn't die or get moved anywhere (even inside the container you borrowed from) before borrows to it all die.
Because of this you are better off with borrows that are short lived and local. Often it's better to keep the index of an element of a Vec instead of the borrow of this element. If you must create types that contain borrows you must know that they become borrows themselves and you need to treat them exactly the same trying to limit their scope and life time.
It's hard when you come from any other language because borrows are superficialy similar to pointers or references to objects. So you try to use them as such. And crash into the compiler because they are not that. What's worse their syntax is very minimalistic which triggers intuition that they must be fast and optimal solution for many problems which they sure can be once you fully internalize their limitations but not a moment sooner.
Another thing is that values in Rust must have the fixed size. So even as simple thing as a string requires a bit of hackery. Basically in Rust the default strategy to have something of variable size is to allocate it on the heap and treat pointer to it (possibly with some other fixed sized data like length) as the fixed sized value you can move around clone and borrow.
So if you want to have semantics you know from other languages you can't just use basic Rust syntax.
You need constructs such as Box and Rc, Cell, RefCell. Make your things clonable and sometimes even copyable and avoid creating borrows whenever possible initially. When you do it Rust becomes as flexible as any other language and you can use it pretty much just as comfortably. Then the value semantics shines as you can very easily compare your data by value, order it, create operators for it, create has for it so you can keep it in HashMaps and HashSets. Then it's delightful.
My advice is when you create a long lived type just wrap it in Rc and treat this Rc as your 'object'. And avoid borrows in your types unless you have a very good performance (measured) reason to have them or you are creating something obviously dependant and usually short lived like an iterator.
Re: Should small Rust structs be passed by-copy or by-borrow? (2019)
#67I always prefer by-borrow. That's because in the future this struct may become non-copy and that means some unnecessary refactoring. My thinking is a bit like "don't take ownership if not needed" - the "not needed" part is the most important thing. Don't require things that are not needed.
And I would bet 9 times out of 10 it won't be the bottleneck or even make a measurable difference.
Re: Should small Rust structs be passed by-copy or by-borrow? (2019)
#68This is one of the problems I have with writing rust code. You have to think about so many mundane details that you barely have time left to think about more important and more interesting things.
As a Rust beginner that likes to learn the hard way I think I have some insights why Rust seems cumbersome and/or hard for programmers trying it. Rust uses syntax that feels familiar but means completely different things than in pretty much any other language. For example '=' doesn't mean assign handle or copy. It by default means move. 'let' doesn't mean create a name for something. It means create physical space fo…
One non-tree cross-link or back-link and you'll have to redesign your entire code.
Re: Should small Rust structs be passed by-copy or by-borrow? (2019)
#69Re: Should small Rust structs be passed by-copy or by-borrow? (2019)
#70I would go with the version that gives the clean user interface (that is, by copy in this case). If it turns out that the other version is significantly more performant and this additional performance is critical for the end users consider adding the by-borrow option. The clarity of the code using a particular library is such an big (but often under-appreciated) benefit that I would heavily lean in this direction whe…
Agreed - and this applies in nearly every language: start simple, trust your compiler, and optimize only when performance becomes untenable.
Trusting the compiler also means knowing what the compiler actually understands & handles vs. what's a library-provided abstraction that's maybe too bloated for its own good and that quickly becomes "not simple" depending on your language of choice.