Live data from Hacker News

Should small Rust structs be passed by-copy or by-borrow?

forrestthewoods.com

71–80 of 115 posts

Re: Should small Rust structs be passed by-copy or by-borrow?

#71

Earlier quoted context omitted.

Yes - even if you ask for pass by move, it might pass a reference for a large struct.

That's interesting, and a bit surprising. I know Ada does this, but it's explicit about only specifying semantics ("in, "out" or "in out") and letting the compiler decide. Surely C or C++ optimizers won't do this?

Let's disregard inlining etc, so that we have actual function calls in both languages.

C and Rust compilers do the same thing - they follow a calling convention. It will tell you how to pass a struct, if it should be passed as a pointer or in registers etc.

Re: Should small Rust structs be passed by-copy or by-borrow?

#72

You should be careful trying to apply these micro benchmarks to modern optimising compiles, behaviour may be different depending on the exact code being compiled. Rust especially has immutability and known types in generics at compile time, so the compiler can do a lot of pointer and inlining magic that C and other languages can't. In the below example Rust switches to using a pointer when you might think it's doing…

0. Generously use black_box 1. a large sample size 2. a single thread 3. high-precision monotonic timestamps on an unloaded system 4. mean/std dev from more than several runs

Or frameworks helping you do so - like "benchmark" for C++ (though I wonder if there is a cross-language one - that does the same amount of "pre"-warming, same counters, etc.)

Probably not... and too much to ask.

Re: Should small Rust structs be passed by-copy or by-borrow?

#73
How to design your API in Rust (or any modern language):

1) What makes the most sense semantically? (So that using your API correctly is the most obvious path)

2) Doesn't matter? Okay, what's the most ergonomic?

3) That code path needs to be performant? Try variations and benchmark within your overall application.

I'm not refuting/dismissing the article. Dives into how the compiler handles things is always interesting! But the programming ecosystem is going through some growing pains right now.

At the beginning of time we had the Era of Assembly; back when our machines were measured in MHz. Code had to be performant above all else; that was the only rule.

During the Era of Moore, performance exploded. As a counter-reaction to the Era of Assembly, programmers began chanting the mantra "No Premature Optimization!" This led to the creation of easy/lazy languages like Python, where code was an art and performance wasn't even an afterthought.

Now we're in the Era of Types. While Python and Javascript were running rampant without types (quack quack), languages like Haskell were evolving in the shadows with intricate and expressive type systems. The fruits of those labors are sprouting in the form of Rust. The mantra of "No Premature Optimization!" isn't enough. We no longer need to choose between code that is easy to write and code that is performant. With a good type system we can be more explicit with the semantics of our code and APIs. This makes our code easier to use, more ergonomic, and gives the compiler more information that it can leverage to optimize our creations into assembly machines that would make C64 developers nod approvingly (though knowing secretly that they could always do better).

The growing pain is this transition from a mindset of just "No Premature Optimization" where the focus is simply on writing "easy code" in stark reaction to the hyperoptimization of the Era of Assembly, to a modern mindset where we should write code with intentionally designed types and semantics. Hence my more complicated list of three rules instead of one.

Side Note: And of course, as some comments have pointed out, step number 3 of my guideline is fraught with peril because the compiler's behavior, and the behavior of the CPU, change like the direction of the wind. Thus the thrust of my comment is emphasizing the use of our new typing systems. If your code is typed correctly and your intentions clear (per step #1), the compiler will do the right thing on average at least.

Side Note 2: None of those guidelines apply to languages of the older eras; they aren't expressive enough to communicate with the compiler and hence you're left in the old miasma of untyped languages where optimization is near hopeless, or languages with type systems so narrow that you're battling day-to-day to build APIs with a semblance of usability.

Re: Should small Rust structs be passed by-copy or by-borrow?

#74
It's wild how much longer this article was than it needed to be. The first benchmark showed that for the test case tested, the copying and borrowing performed nearly identically. The answer to the stated question is that one is free to use whichever syntax one prefers. I would pick the one with the less-confusing semantics, as less complex code is more likely to be correct.

Re: Should small Rust structs be passed by-copy or by-borrow?

#75
post #8

This purports to discuss pass-by-copy or reference but ends up not discussing that particular issue at all since every single compiler naturally inlined and vectorized his trivial micro-benchmark. If you were to discuss the headline issue, I guess you could delve deep into calling conventions, register pressure, various instruction set extensions that compilers can abuse to carry the data.. or you take the compiler o…

I've mostly only dabbled in Rust, but I recall it being really difficult to pass data by copy in Rust. It seemed to want you to pass things by reference, especially if your type is meant to implement any prominent trait. It didn't matter if my type was an int, Rust seemed to want me to pass it by reference.

Re: Should small Rust structs be passed by-copy or by-borrow?

#77
> My answer to by-copy versus by-borrow for small Rust structs is by-copy

The author is making the case for special treatment of structs for a special case. For a performance gain that's probably below the margin of error. This is negating the elegance and consistency of rust for a special case. I think the author thinks too much in C++ terms and is sort of missing the point of Rust (or at least one point of rust).

Re: Should small Rust structs be passed by-copy or by-borrow?

#78
post #77

> My answer to by-copy versus by-borrow for small Rust structs is by-copy The author is making the case for special treatment of structs for a special case. For a performance gain that's probably below the margin of error. This is negating the elegance and consistency of rust for a special case. I think the author thinks too much in C++ terms and is sort of missing the point of Rust (or at least one point of rust).

> The author is making the case for special treatment of structs for a special case.

The struct by-copy is mostly a good API thing in a lot of scenarios, particularly when the objects are Even more so, when you're only writing half the code and want to separate the actions within a function from any side-effects outside.

I would say that the performance difference for small structures is irrelevant to the extra hop, particularly if the copied location is the stack on the callee.

Re: Should small Rust structs be passed by-copy or by-borrow?

#79

The Cpp Core Guidelines suggest that the cutoff between copy and const reference should be "up to two or three words" (item F.16). Following this advice, the f32 vector would be passed by value, and the f64 vector would end up in the grey zone. https://github.com/isocpp/CppCoreGuidelines/blob/master/CppC...

That explains why I usually see std::span passed by value.

Spans with extent known to the compiler are not even two words long. Like unique_ptr with default deleter they are the size of a pointer.

Re: Should small Rust structs be passed by-copy or by-borrow?

#80

This reminds me of the years I wasted writing C++, always looking for the idiomatic approach, rather than learning about security, networking, architecture and so on. In exactly the same way, Rust is just too complicated.

As someone said on the thread, there is a clippy suggestion to tell you that you could pass by copy given the size of your struct (without it I would never have passed a struct by copy).
Post reply on HN