One of the best interview questions I ever came up with was “Why might you choose to implement something yourself instead of adding a dependency?”. At least one candidate was like “I wouldn’t do that”.
As often with engineering, the answer to the question of adding a dependency rather than writing yourself is not a simple “yes” or “no”.
what’s the ecosystem like for solving that problem? Are there established libraries of good quality?
is the problem to solve a core business problem or a support function?
how complicated is the problem to solve?
are there aspects of the problem that are specific to my situation and that solving myself is likely to bring my solution a performance/feature advantage over the generic library solution?
The response to these questions may change with time, too. But generally, I’m not going to waste my employer’s money reimplementing JSON parsing when simd json exists, and on the other hand I’ll not typically delegate our core functionality to dependencies. Part of maturing as an engineer is becoming better at making these calls and getting to know when they need revisiting.
I’ll grant that if there is no good existing library out there for what you need that is probably a time to build. But ideally it’s a time to build a library so that the next person doesn’t have to.
ah, nice way to put it, I share the same observations.
“Taking a page from my grimoire” is how I call that process of reusing old recipes that worked for tools that require a lot of ceremony and fail in inscrutable ways.
Personally the biggest offender is LaTeX. I don’t use make or CMake because I’ve been blessed with cargo.
Which features that were stabilized in the last 12 months do you use?
Absolutely none. For most of them I don’t even know what they do (as is the case for many of the entirely inscrutable type design RFCs).
I’m glad the language design people get to do their thing but as in every other programming language I think at some point it becomes a complexity trap.
I think the way Rust uses the term “features” might make it sound like more than it is. In a product a new feature is often an entirely new use case/ system. In Rust it’s often just closing an inconsistency. Like, why can I use impl Trait here but not there? That doesn’t increase complexity, it reduces it.
I disagree with that, I think you’re being a bit too optimistic.
Rust’s multiple new features are more things you need to learn because even if they are possibly simplifying the language there’s so much code out there that doesn’t use those simplifications that you’ll have to learn both. Then you get to the issue of coding style and consistency: not everybody will want to use feature B instead of A because they’re already used to A, even if B is more consistent. Or they’ll use both and it’ll complicate the code base. Impl at the return position is one example.
It’s why some languages work so hard to not fall into that improvement trap, even being reviled for not changing.
We can disagree I guess. I think that “you can use the exact same feature that has existed for years but in a new place” is not increasing complexity and I feel strongly that it reduces complexity.
The vast majority of features that were stabilized in the last 12 months were simple language changes, like allowing exclusive ranges in patterns, C string literals, and #[expect], or additions to the standard library.
I guess that’s fine, most day-to-day stuff is already finished.
I really appreciated the new const and asm features this year FWIW, whereas the async stuff doesn’t affect me as much. It depends on your use cases.
There are some features I’m still looking forward to (const generics, if-let chains, never type, try blocks) all of which I can understand someone else not needing.
To offer another point of view, I really miss a lot of the discussed features, most notably the allocator trait, generators, and type alias impl trait.
I really don’t get it. When on the rust side, use a struct with a Drop implementation, not the ugly C FFI API?
Also, a lot of the post is about how memory alloc’d on the rust side must be freed on the rust side: yes, for instance the rust code is allowed to override the global allocator.
I noticed this author made some particularly curious points in their last post about doing Rust FFI.
They seem to be in this very strange and particular point where they don’t know much Rust, are only using Rust for weird FFI wedges, and then form strong opinions about Rust from encountering these edge cases while not having enough experience with Rust to actually do some of this with full competency. I really don’t know how to tell the author that they’re holding it wrong. They’ve already evaluated Rust as not working for them, I’m not sure why they’re still going.
I found this similarly confusing. My experience writing FFI glue code in Rust is that it’s not painless, but not nearly as painful as the author found it: the normal “new/destroy” pattern is easily handled by Box::into_raw(Box::new(...)) and drop(Box::from_raw(...)), which has exactly the allocation/free semantics that C programmers are used to.
I can tell the article is by Raymond Chen just by the title. This is what genuine talent looks like.
As always interesting discussion, i think I’ll still the “lav”/“law” comparison, really makes it easier to understand why diacritics and digraphs are not the same as their latin counterpart or the separate letters
As always, these articles are about nasty unexpected bahavior, not just UB.
I was surprised to read that destructors are implicitly noexcept — that doesn’t match my experience. I’ve written destructors that throw, and they work correctly without compiler warnings or calls to std::terminate. What’s going on?
(You do have to be careful when throwing from a destructor. If the destructor is called as part of stack unwinding while an exception is being thrown, any call to ‘throw’ will terminate: you can’t have recursive exceptions. Fortunately you can detect this situation by calling … damn, a std function whose name escapes me and I’m in the living room on my iPad and don’t feel like looking it up. Something like std::active_exceptions()?)
Other than that, cpp reference clarifies that throwing from destructors, while discouraged, is allowed, and will work as usual in a non unwinding context (but std::terminate if thrown from an unwinding context).
The situation for noexcept is less clear. It is saying that you “usually needs to declare noexcept(false) on a destructor that throws exception”, but doesn’t clarify the cases where this is not needed: https://en.cppreference.com/w/cpp/language/destructor
I remember marking dtors as noexcept(false) in the rare cases where they could throw.
For comments it’s harder to judge since I don’t think normal users have easy access to all comments of other users. What I see is ~33 comments out of which ~14 (42%) are under self-promo stories.
In both cases you are far above the maximum threshold.
Which, again, boils down to visibility of this rule and how it’s phrased.
You’ve been there for at least seven years and you’ve probably caught 10s or 100s of people breaking the rule, you might have participated in discussions to introduce this, or maybe you have even contributed the text of the rule yourself. This I don’t know, and it’s none of my business to be honest.
Compare this:
As a rule of thumb, self-promo should be less than a quarter of one’s stories and comments.
To this:
In both cases you are far above the maximum threshold.
I don’t know about you, but for me there’s a substantial difference between a rule of thumb and maximum threshold.
One sounds pretty much like
This is roughly how much is okay-ish, crossing this line is distasteful and you will make yourself look like an egocentric buffoon.
The other is more explicit
This is the maximum we can tolerate. If you cross the line, you are in trouble.
According to first interpretation, @danthegoodman1 is just another guy who’s too much into self promo and this is distasteful. According to the second, he’s waiting for a banhammer to come at him.
When I ran into this myself, and admitted I totally overlooked this point and why, the user who pointed this out started pointing out other instances where I missed one of the rules, including ones on the bullet list on submit page itself.
I don’t know about you, but for me there’s nothing friendly about it, and it’s not even a warning anymore if someone apologizes and explains themselves, and the other person goes an extra mile to show you how unworthy you are.
I don’t see it the way you do (but I’m just a regular user without any say here). The way I read the rules is that you should keep the ratio below 25% unless you write exceptionally insightful content (and by default, you should assume that you’re not).
But this is beside the point, however you interpret the wording, @danthegoodman1 is not following the rules and should correct their behaviour here. No one mentioned any banhammer.
The way I read the rules is that you should keep the ratio below 25% unless you write exceptionally insightful content (and by default, you should assume that you’re not).
Where do the rules mention the quality of the content?
No disrespect intended, but there’s a way to report me to the mods if you feel I’m violating rules, and they can speak to me about it. But being such a vigilante in the comments does not promote a healthy community.
I left a short comment about the rule that you are violating. You disagreed (without any explanation), so I showed you numbers that prove my point. Do you think this is vigilantism?
but there’s a way to report me to the mods if you feel I’m violating rules
Reporting to the mods has two downsides. First, mods are already doing their job for free and since this is not something that needs immediate action from them, there is no point burdening them with it. Second, posting publicly serves as a reminder to others who will see this message that such a rule exists.
The explanation was in the comment above it, which I agreed with if you scroll up.
Based on your last sentence it seems this has devolved into a pissing contest, so I think for the better of the thread and community we should agree to end here.
It depends how the rule is interpreted. If it is 1/4 of stories and 1/4 of comments, it is not the same as 1/4 of (comments and stories counted together)
I interpret it as 1/4 of all stories and comments counted together. I think it’s totally fine to only post your own stories but engage in the community through comments. For an extreme example of this, see @david_chisnall. Almost every story he’s submitted is either authored by him or about CHERI (which is a project he is heavily involved in). So by that metric he’s clearly only in it for self-promotion. But he also comments extensively on other stories. Blindly applying this rule to stories and comments separately just doesn’t make sense.
I interpret it as 1/4 of all stories and comments counted together.
FWIW, that is also how I understand the guideline. Even by that understanding, I think the # of stories plus the # of comments we can see in the poster’s history probably put them a little above the line, but they’ve posted enough comments that only a mod could see if that’s their usual pattern. And it’s just not interesting enough, IMO, to jump onto IRC and ask them to count.
I guess that depends on how one interprets the “rule of thumb”: “(keep one’s own work less than 1/4 of one’s stories) AND (keep one’s own work less than 1/4 of one’s comments)”, or “keep one’s own work less than 1/4 of (one’s stories and comments)”. (For context: danthegoodman1’s stories are 11/16 (=68.75%) marked as self-authored.)
This is from January – before the issue that emerged with Wedson. Rust ecosystem indeed seems to have an issue with burnout. And I know I might be wrong, but I can’t help but wonder how much of that has to do with the language itself.
I wonder how much longer conversations around Rust code are versus same typical code in a language like C or Java. Because Rust forces a very strict line of concerns, at every corner, you are not only debating with a counter-part, but you and the counter-part are having a meta-debate with the language and the ecosystem.
In other words, instead of the conversation being like: “What is the right approach to solve this particular problem at hand?” becomes: “What is the right approach to solve this particular problem at hand while accommodating the Rust compiler requirements?”
Overall, we’ve seen no data to indicate that there is any productivity penalty for Rust relative to any other language these developers previously used at Google.
I don’t find myself discussing “how do I accommodate the compiler” much when writing Rust code. It’s just a programming language. It’s not that different from any other.
This is what I call Rust brain (I used to call it Haskell brain) – that happens when someone can automatically code switch between languages. This is a uncommon gift and, in my opinion, should not be generalized to a large population.
Overall, we’ve seen no data to indicate that there is any productivity penalty for Rust relative to any other language these developers previously used at Google.
from …
Pulling from the over 1,000 Google developers who have authored and committed Rust code as some part of their work in 2022
There seem to be selection bias in this post you shared.
I would like to see a bigger and more diverse sample set.
Selection bias is the bias introduced by the selection of individuals, groups, or data for analysis in such a way that proper randomization is not achieved, thereby failing to ensure that the sample obtained is representative of the population intended to be analyzed.
From the link:
Pulling from the over 1,000 Google developers who have authored and committed Rust code as some part of their work in 2022
There are two selection biases made: one, it only take googlers; and two, googlers who committed rust code. And then, this articles goes on to make general statements. In order to make general statements, they’d have to produce a sample selection that is also general.
For example, for this rumor to be accurate, they would have to change their claim from
Rumor 1: Rust takes more than 6 months to learn – Debunked !
to
Rumor 1: Rust takes more than 6 months to learn among Googlers who commit Rust code - Debunked !
Rumor 1: Rust takes more than 6 months to learn among Googlers who commit Rust code - Debunked !
You can’t learn Rust without committing Rust code. That’s not selection bias. That’s an inherent part of asking this question.
Furthermore, it’s more complex than just “people who authored and committed Rust code.” The group in question was a mixture of people who had Rust experience before, and those who did not. Only 13% had previous Rust experience. That means that 87% of these folks would not have had “Rust brain” before this study kicked off.
I do agree that results outside of Google would be great to have. But I think you’re really stretching here.
You can’t learn Rust without committing Rust code.
I am going to assume that you meant “You can’t learn Rust without writing Rust code”. I agree with this statement.
Not every one that is trying to learn Rust will actually commit Rust code at Google - either by self-selecting into not doing it, or for lacking opportunity of doing it. Even inside Google, for these rumors to be validated, they would have to also look for people who tried and failed - which by the definition of their own sample, they haven’t.
When I claim there is selection bias, I am not implying anyone was dishonest - all I am saying is that good statistics and science is hard. This article fails at that.
But I think you’re really stretching here.
I would counter though, that claim that Rust is just another programming language is also a stretch. I like Rust and I’ve been learning it for while, and I can safely say it is a paradigm shift – and not just another programming language, in any sense.
Okay, in theory, someone could have quit their job because they couldn’t learn Rust. I’d find it hard to believe that that’s a large enough percentage of people to meaningfully change the outcome.
There’s a whole lot of “blue collar programmers” out there who know only one or perhaps several similar languages (think Java, C# and C++ or PHP and JS). Those aren’t the types who tend to frequent forums like lobsters or the Orange Site, but still they’re most likely the majority.
In my experience, adding constraints on what code compiles makes choosing the right approach simpler, not harder.
I’m just a Rust practitioner, not a language contributor, but I feel like the reasons laid out by the author, that are mostly non technical, suffice to explain a burnout
This is from January – before the issue that emerged with Wedson. Rust ecosystem indeed seems to have an issue with burnout. And I know I might be wrong, but I can’t help but wonder how much of that has to do with the language itself.
Wedson obviously burned out because the Linux kernel is a burnout machine.
I think it’s a hole in Rust’s design that user code can’t make types that behave like references. First-class references can be (re)borrowed for &self, but when you make your own type that is supposed to behave like a reference, it has to be Wrapper<'a>, rather than something like &'a Wrapper.
Borrowing such newtype creates a double reference &'b Wrapper<'a> and now you have two levels of indirection and two lifetimes.
Copy or not, it doesn’t help with &self methods. Nearly all traits use &self, and self would mean something different for types that aren’t a wrapper around a reference.
Having to explicitly call reborrow() instead of &value or automatic coercion is another example where it shows that newtype wrappers don’t work like first-class reference types.
Interesting article. I think variance is unintuitive in Rust because it is so hidden. Ideally, the variance should appear in the lifetime notation. It would allow to express that, e.g., a covariant lifetime is needed for a trait.
Currently, the places where variance cannot be expressed are mostly supposed to be invariant, ergo the issue encountered in the post.
That’s s great link, thank you. I’ve only informed myself about how it works in Rust. But I wonder how other languages deal with it. We might be able to learn from them?
I’m coming to the issue from the perspective of the user of types containing lifetimes, not the implementer of these types.
I argue that inferred variance makes it hard to consume types containing lifetimes and generic parameter, because one needs to check the internals of the structure to know how it behaves regarding variance.
It also indirectly makes the language inexpressive when wanting to talk, e.g., about the kind of types that are covariant on a lifetime (I don’t think you can implement a is_covariant “type trait” or “where bound”)
Even for implementers, it gives raise to compatibility hazards when a struct that should be covariant is accidentally made invariant, or when a struct that ought to be invariant omitted the correct PhantomData to make it so.
I posit that explicit annotations would put us on a path of adding more expressivity for variance, and a better understanding of variance.
Yes, variance is hard. That’s why I think we should make it super visible.
This proposal is interesting in that it maximalizes safety for C++ while minimizing interoperability costs.
I find the difference with Rust<->C++ is instructing to consider:
single toolchain means no headache like trying to compile in multiple indépendant rust .a without duplicating the runtime, std and common dependencies
access to C++ templates
The list of advantages pretty much stops here. Introducing safety requires rewriting both for rust and safe C++. The stdlib has to be different in both cases. The limitations due to borrow checking will largely overlap.
This sets a clear goal for Rust and C++ interop IMO
Nitpick, but I couldn’t find where signed integer overflow is being discussed in safe C++. Would love to know how this is handled
Wrapping is not necessarily safe (though it’s safer than UB). The most common integer overflow vulnerabilities come from multiplying an attacker-provided value with something and having it wrap, so now you have a smaller value than you expected.
It only becomes a memory safety issue if it then serves as an index in a buffer. Safe C++ already guards against these by having checked access in std2
That’s true, but attacker-controlled wrapping values can still cause you to index the wrong (in-bound) element in an array, which may be a reference to a different security context. I’ve seen vulnerabilities from wrapping integers that were not memory safety bugs.
That’s true. I was talking in the context of “safe C++” providing memory safe C++.
To avoid non-memory overflow vulnerabilities, Rust provides checked_add, saturating_add, and wrapping_add. From a security standpoint I wish the language banned arithmetic operators (or provided multiple ones to concisely express the desired semantics). There’s a clippy lint but it is allowed by default and I might have a hard time proposing to make it a warning in projects I contribute to.
I like the Smalltalk default of an integer type that silently promotes to a heap allocated big integer on overflow, but that’s vulnerable to denial of service attacks and truncation problems. It’s quite easy in C++ to define wrapper types for wrapping and saturating arithmetic. Saturating is often a better choice than wrapping since there’s a single value for all cases of overflow.
If an attacker-provided value causes UB, and the compiler has generated code that assumes UB cannot occur, then the attacker can still trigger behaviour that violates the C++ virtual machine.
I don’t think that a “safe” subset of C++ can be truly safe if it has any UB. Memory safety eliminates an important subset of UB but not all of it.
Memory safety eliminates an important subset of UB but not all of it
I think you have this wrong: memory safety requires that there is no UB, at least in the C++ sense of UB, since any UB is a violation of memory safety.
and the compiler has generated code that assumes UB cannot occur, then the attacker can still trigger behaviour that violates the C++ virtual machine
This is a common misunderstanding, and sometimes a useful simplification, but compilers do not “generate code that assumes UB cannot occur”. They generate code that satisfies the requirements of the C++ abstract machine, which is trivial in the presence of UB because there are no requirements in that case. “Behaviour that violates the C++ virtual machine” seems to be something of a mixed metaphor or other conflation.
compilers do not “generate code that assumes UB cannot occur”
I think that these are equivalent. Since any code is a valid implementation of undefined behaviour, the compiler can generate code that assumes that UB won’t occur. This is optimal and correct.
Of course as the programmer you can’t assume this. But it is a useful equivalence to understand when trying to debug something.
The end result is equivalent, but that does not mean that the two things are the same.
What you have described is the essence of the useful simplification I mentioned. It is often expressed instead as “the compiler assumes that UB can’t occur” (meaning that the compiler was written based on an assumption that UB can’t occur, and generates code based on that assumption) and that’s problematic because some people have trouble comprehending that UB not having defined semantics is satisfied by a code generator that ignores code paths necessarily containing UB, and conditions that imply UB, and claim that (for example) pretending UB doesn’t happen is “unsound” because it certainly can happen.
Hence, I would rather be precise: there is no assumption that UB doesn’t happen, rather, code generation typically does not constrain its output based on code paths or conditions that necessitate UB. The effect in terms of generated code may be the same, but the latter does not imply that compilation is based on a faulty premise, which it is not.
I don’t find this article a good defense of exceptions. It has a strong bias towards a specific error handling scheme: “retrieve error at the top level, log it and continue”. This basic scheme hides a lot of the drawbacks of exceptions. Furthermore, it keeps conflating the properties of exceptions and results with things that are independent of these two, but relevant to the languages being considered.
Exceptions Are Much Easier to Work With
For the initial author of the code, they might. The hidden early returns caused by exceptions will make maintenance harder, though.
Exceptions make this extraordinarily easy. No matter where you are, 100 call stacks deep in your little corner of the program, you detect an error, so you throw an exception. All the hard work is now done automatically in stack unwinding
Having functions that take result on the entire stack is what you want and will give you the same benefit. This is more about having RAII than exception vs results.
Boilerplate
? is no boilerplate. It is a single character, and it is load-bearing. It means that the operations is fallible.
Do you add an error result whenever a function asserts something?
No, Rust has panics for this, which has the same as exceptions.
Allocations can fail, the stack can overflow, and arithmetic operations can overflow. Without throwing exceptions, these failure modes are often simply hidden.
Conflating the decision of aborting on memory allocation failure (motivated by Linux overcommit behavior) with the mechanism used to return the error. Ironically, the “simply hidden” failure mode in the case of arithmetic operations describe a case where Rust would panic, so throw an exception.
build some more reliable interfaces that don’t panic, e.g. Box::try_new()
This interface is useful in a kernel context where you know that there is no overcommit, and completely useless in an application where the memory will be happily returned to you, and then the OOM killer will kill your process. Anyway, it has no bearing to exception vs result, as try_new returns a result, so the point being made is unclear.
Exceptions Lead to Better Error Messages
This paragraph is about the dilemma of putting context in the error type or fetching it from the environment. It applies all the same regardless of whether you’re using exceptions or results.
Exceptions Are More Performant
This is generally mitigated a lot in the presence of inlining. It is also more the characteristics of the current implementations that something that will necessarily remain true.
I’m still a bit puzzled as to why newer languages like Rust or Go don’t allow the use of exceptions.
Rust does, in the form of panics. They do occupy some space in the error-handling story, taking in on some use cases where they are a better fit than Result (e.g. asserts)
Rust does, in the form of panics. They do occupy some space in the error-handling story, taking in on some use cases where they are a better fit than Result (e.g. asserts)
Go also has panics, and AFAIK they’re also used in those situations (e.g.: OOM). So yes, while I prefer Exceptions than Errors, I also can’t concur with the author points.
Or RE2, it’s not the fastest, non-movable objects are annoying, and their use of an in-place Compile instead of a builder is infuriating to me, but it’s reasonably easy to use and the lack of backtracking makes it very attractive as a default.
You’re probably alluding to this with “lack of backtracking” - I’ll point out that the most notable property of RE2 is that it has linear time complexity in the length of the pattern and the length of the string (O(m*n)), making it much less vulnerable to DoS attacks if one or both of them is user-generated input. Russ Cox has a series of articles on this.
That’s my concern as well. I haven’t ever felt the pain of cloning Arcs to be a significant enough burden to justify the complexity of a lightweight clone, or implicit-cloning even limited only to smart pointer types. The lightweight clone trait seems to have all kinds of downsides from the little bit I’ve seen. And I’d worry about implicit cloning in a general case for obvious reasons (not that this is being considered), or even when limited to ref counting pointers feels not great to special case certain types for such a fundamental trait. If they were going to want syntax-less clones for ref counted pointers it feels like they should have used Copy, but that feels wrong too?
I was originally concerned with implicit clones as well (I can’t remember what the working name for it anymore), but I’ve been convinced it’s probably for the best. I, too, don’t have a problem with cloning things, but I empathize with those that do. I feel like the extra complexity involved in making some sort of simplified/implicit cloning system is worth it; not for the sake of people like us who are willing to put up with Rust’s quirks so we can take advantage of it’s benefits, but for everyone else who isn’t so enamored by the language. Explicitly writing redundant clones everywhere just to pass a thread boundary does look rather silly, that’s hard to deny. A programming language is about encoding intent, not raw machine instructions, otherwise we’d all just program in machine code. Rust’s overt explicitness is a joy, but in instances like this, it feels a little redundant and not particularly worth the effort to maintain that explicitness in this situation. I think for just this one particular issue, this is where we can let Rust “do what other languages do” and hide the cloning behind some convenience syntax. However, I will say I would prefer the “implicit clone” to be somehow explicit, like through an operator, like how & lets you take a reference.
As for making Arc copyable, the issue there is that Copy can’t be implemented on types that store anything that isn’t Copy (as far as I know). To manually do that behind the scenes would be atrocious, I’m already uncomfortable with how long it’s taking the core team to make std::ops::Try stable. If one type can do something (and it’s not a primitive), all types should be able to do it.
Yes, if a move closure could basically get Copy-ish semantics and end up with an automatic Clone of things that are appropriately marked, a lot of fluff would disappear in a lot of Arc-heavy threaded code I work with!
Well, you do need to use spawn to start up new tasks and you need to explicitly clone Arcs ahead of time in order to pass shared data in. I don’t think there’s any way to avoid this at the moment, but I’d love to see an example of sharing data between tasks without it.
It’s true that you don’t need to do explicit clones once you’re in the async block for the task. I think the reason why this is worth fixing from an ergonomics point of view is that it’s literally the first thing you do when writing an async Rust program, so it’s an obvious stumbling block for anyone not used to Rust’s “clone the shared pointer before moving it into the closure or async block” pattern.
I use spawn everywhere, yes. I basically program in an actor paradigm. My primary use of cloning Arc is to give each task an Arc of the program config, at the very least, but of course I’m also always passing in some other state for them tor read or modify, like some kind of “world” mutex for each task to get a handle to their siblings to communicate with them, if I need that.
Although honestly it hasn’t been much of a pain for me, since I simply construct the structs for these “actors” in-place where I spawn them, so any Arc cloning is easily drowned out by the rest of the struct construction.
Once, I had a colleague come to see me with “a segfault”.
As usual I started debugging it in valgrind and ASAN/UBSAN.
I’m a bit fuzzy on the details, but one of the sanitizers would mysteriously not exhibit the issue, while the other would crash with a very unhelpful message. As for valgrind, it would itself segfault when ran on the code.
The debugger was looking very confused as to the size of an object.
Turned out that my colleague had closed an anonymous namespace too early, in two different files.
He had meant to write:
namespace {
// ... some other stuff ...
struct InternalDetails {
SomeType _field_1;
}
}
but he had written:
namespace {
// ... some other stuff
}
struct InternalDetails {
SomeType _field_2;
}
Another file with the same mistake would declare another InternalDetails struct with a different layout.
That a single typo would lose us as much time as 2 afternoons of 2 engineers with no tool able to detect it was infuriating to say the least.
We added a rust-toolchain.toml file at the root of our repository, and we’re using the helix rust actions that take that file into account when no toolchain argument is specified.
This way we are not broken when a new Rust version releases.
This is especially vital for us because we release on Mondays and we are working 4-days weeks with Friday off, and in our timezone the Rust Release time is on Thursday afternoon.
We don’t want “the release build is suddenly broken on Monday because a new Rust version released” to be a thing.
We have automation on our repository to create an issue about updating the Rust version every six months. We generally tie it with an update of our dependencies (we also have dependabot for security deps)
This new articles reinforces my thoughts about the design:
Claim should not be a trait, but an annotation on Clone implementations, lest it inevitably ends up used in a bound where it shouldn’t
Tying the “automatic copyability” of types to their size is going to cause a lot of backward-compatibility issues because adding a Copy field to your struct won’t guarantee that it remains moveable. It also has weird interactions with future extensions of const generics (think [T; N])
The more I read about the design, the more I think that if the gain is just about clone ergonomics and preventing a performance pitfall, then the gain doesn’t outweigh the cost
One of the best interview questions I ever came up with was “Why might you choose to implement something yourself instead of adding a dependency?”. At least one candidate was like “I wouldn’t do that”.
Hire that one
See, I disagree!
As often with engineering, the answer to the question of adding a dependency rather than writing yourself is not a simple “yes” or “no”.
The response to these questions may change with time, too. But generally, I’m not going to waste my employer’s money reimplementing JSON parsing when simd json exists, and on the other hand I’ll not typically delegate our core functionality to dependencies. Part of maturing as an engineer is becoming better at making these calls and getting to know when they need revisiting.
I’ll grant that if there is no good existing library out there for what you need that is probably a time to build. But ideally it’s a time to build a library so that the next person doesn’t have to.
Right that’s a strong hire response I think.
ah, nice way to put it, I share the same observations.
“Taking a page from my grimoire” is how I call that process of reusing old recipes that worked for tools that require a lot of ceremony and fail in inscrutable ways.
Personally the biggest offender is LaTeX. I don’t use make or CMake because I’ve been blessed with cargo.
Absolutely none. For most of them I don’t even know what they do (as is the case for many of the entirely inscrutable type design RFCs).
I’m glad the language design people get to do their thing but as in every other programming language I think at some point it becomes a complexity trap.
I think the way Rust uses the term “features” might make it sound like more than it is. In a product a new feature is often an entirely new use case/ system. In Rust it’s often just closing an inconsistency. Like, why can I use
impl Traithere but not there? That doesn’t increase complexity, it reduces it.I disagree with that, I think you’re being a bit too optimistic.
Rust’s multiple new features are more things you need to learn because even if they are possibly simplifying the language there’s so much code out there that doesn’t use those simplifications that you’ll have to learn both. Then you get to the issue of coding style and consistency: not everybody will want to use feature B instead of A because they’re already used to A, even if B is more consistent. Or they’ll use both and it’ll complicate the code base. Impl at the return position is one example.
It’s why some languages work so hard to not fall into that improvement trap, even being reviled for not changing.
We can disagree I guess. I think that “you can use the exact same feature that has existed for years but in a new place” is not increasing complexity and I feel strongly that it reduces complexity.
The vast majority of features that were stabilized in the last 12 months were simple language changes, like allowing exclusive ranges in patterns, C string literals, and
#[expect], or additions to the standard library.Exclusive ranges is pretty much the only one I can see myself using.
I guess that’s fine, most day-to-day stuff is already finished.
I really appreciated the new const and asm features this year FWIW, whereas the
asyncstuff doesn’t affect me as much. It depends on your use cases.There are some features I’m still looking forward to (const generics, if-let chains, never type, try blocks) all of which I can understand someone else not needing.
Would https://lib.rs/crates/never-say-never be good enough? It provides access to the unstable never type in stable Rust.
Very clever, I didn’t know about this.
So
!is stable in return position and you can pull it out via the::Outputassociated type of theFnOncetrait?To offer another point of view, I really miss a lot of the discussed features, most notably the allocator trait, generators, and type alias impl trait.
In case you’re not aware, there are libraries that can be used in the interim, like https://lib.rs/crates/next-gen.
Thank you. I am aware, I wrote a library that is generator-adjacent https://lib.rs/crates/nolife
Still, language support is a must
uh? Why isn’t WebGPU supported in Firefox?
Last I heard it was 90% there.
I really don’t get it. When on the rust side, use a struct with a Drop implementation, not the ugly C FFI API?
Also, a lot of the post is about how memory alloc’d on the rust side must be freed on the rust side: yes, for instance the rust code is allowed to override the global allocator.
I noticed this author made some particularly curious points in their last post about doing Rust FFI.
They seem to be in this very strange and particular point where they don’t know much Rust, are only using Rust for weird FFI wedges, and then form strong opinions about Rust from encountering these edge cases while not having enough experience with Rust to actually do some of this with full competency. I really don’t know how to tell the author that they’re holding it wrong. They’ve already evaluated Rust as not working for them, I’m not sure why they’re still going.
I found this similarly confusing. My experience writing FFI glue code in Rust is that it’s not painless, but not nearly as painful as the author found it: the normal “new/destroy” pattern is easily handled by
Box::into_raw(Box::new(...))anddrop(Box::from_raw(...)), which has exactly the allocation/free semantics that C programmers are used to.I can tell the article is by Raymond Chen just by the title. This is what genuine talent looks like.
As always interesting discussion, i think I’ll still the “lav”/“law” comparison, really makes it easier to understand why diacritics and digraphs are not the same as their latin counterpart or the separate letters
Had the exact same thought, saw the title, saw devblogs on Microsoft and opened it immediately, never disappointed!
As always, these articles are about nasty unexpected bahavior, not just UB.
I was surprised to read that destructors are implicitly noexcept — that doesn’t match my experience. I’ve written destructors that throw, and they work correctly without compiler warnings or calls to std::terminate. What’s going on?
(You do have to be careful when throwing from a destructor. If the destructor is called as part of stack unwinding while an exception is being thrown, any call to ‘throw’ will terminate: you can’t have recursive exceptions. Fortunately you can detect this situation by calling … damn, a std function whose name escapes me and I’m in the living room on my iPad and don’t feel like looking it up. Something like std::active_exceptions()?)
You might have been thinking about
std::uncaught_exceptions: https://en.cppreference.com/w/cpp/error/uncaught_exception.There’s also
std::current_eception()that lets you recover a ptr to the current exception object and that will be empty if no exception is in flight: https://en.cppreference.com/w/cpp/error/current_exceptionOther than that, cpp reference clarifies that throwing from destructors, while discouraged, is allowed, and will work as usual in a non unwinding context (but std::terminate if thrown from an unwinding context).
The situation for noexcept is less clear. It is saying that you “usually needs to declare
noexcept(false)on a destructor that throws exception”, but doesn’t clarify the cases where this is not needed: https://en.cppreference.com/w/cpp/language/destructorI remember marking dtors as
noexcept(false)in the rare cases where they could throw.Please try to post less self-promo.
I think this is fine? He comments outside of his own submissions and has a decent amount of comments.
i thought this too but didn’t want to say it
I don’t think so. For stories it’s easy to show that the majority is self promo. You have 16 stories total, and 11 (68%) of them are self-promo:
For comments it’s harder to judge since I don’t think normal users have easy access to all comments of other users. What I see is ~33 comments out of which ~14 (42%) are under self-promo stories.
In both cases you are far above the maximum threshold.
Which, again, boils down to visibility of this rule and how it’s phrased.
You’ve been there for at least seven years and you’ve probably caught 10s or 100s of people breaking the rule, you might have participated in discussions to introduce this, or maybe you have even contributed the text of the rule yourself. This I don’t know, and it’s none of my business to be honest.
Compare this:
To this:
I don’t know about you, but for me there’s a substantial difference between a rule of thumb and maximum threshold.
One sounds pretty much like
The other is more explicit
According to first interpretation, @danthegoodman1 is just another guy who’s too much into self promo and this is distasteful. According to the second, he’s waiting for a banhammer to come at him.
Submitters should treat comments informing them of potential self-promotion violations as a friendly warning.
When I ran into this myself, and admitted I totally overlooked this point and why, the user who pointed this out started pointing out other instances where I missed one of the rules, including ones on the bullet list on submit page itself.
I don’t know about you, but for me there’s nothing friendly about it, and it’s not even a warning anymore if someone apologizes and explains themselves, and the other person goes an extra mile to show you how unworthy you are.
There’s nothing friendly about publicly shaming someone. There’s a report button.
I don’t see it the way you do (but I’m just a regular user without any say here). The way I read the rules is that you should keep the ratio below 25% unless you write exceptionally insightful content (and by default, you should assume that you’re not).
But this is beside the point, however you interpret the wording, @danthegoodman1 is not following the rules and should correct their behaviour here. No one mentioned any banhammer.
Where do the rules mention the quality of the content?
No disrespect intended, but there’s a way to report me to the mods if you feel I’m violating rules, and they can speak to me about it. But being such a vigilante in the comments does not promote a healthy community.
I left a short comment about the rule that you are violating. You disagreed (without any explanation), so I showed you numbers that prove my point. Do you think this is vigilantism?
Reporting to the mods has two downsides. First, mods are already doing their job for free and since this is not something that needs immediate action from them, there is no point burdening them with it. Second, posting publicly serves as a reminder to others who will see this message that such a rule exists.
As opposed to excessive self-promotion? ;)
The explanation was in the comment above it, which I agreed with if you scroll up.
Based on your last sentence it seems this has devolved into a pissing contest, so I think for the better of the thread and community we should agree to end here.
It depends how the rule is interpreted. If it is 1/4 of stories and 1/4 of comments, it is not the same as 1/4 of (comments and stories counted together)
I interpret it as 1/4 of all stories and comments counted together. I think it’s totally fine to only post your own stories but engage in the community through comments. For an extreme example of this, see @david_chisnall. Almost every story he’s submitted is either authored by him or about CHERI (which is a project he is heavily involved in). So by that metric he’s clearly only in it for self-promotion. But he also comments extensively on other stories. Blindly applying this rule to stories and comments separately just doesn’t make sense.
I tend to agree, but also I’m not the most experienced to interpret the rule 😅
FWIW, that is also how I understand the guideline. Even by that understanding, I think the # of stories plus the # of comments we can see in the poster’s history probably put them a little above the line, but they’ve posted enough comments that only a mod could see if that’s their usual pattern. And it’s just not interesting enough, IMO, to jump onto IRC and ask them to count.
I guess that depends on how one interprets the “rule of thumb”: “(keep one’s own work less than 1/4 of one’s stories) AND (keep one’s own work less than 1/4 of one’s comments)”, or “keep one’s own work less than 1/4 of (one’s stories and comments)”. (For context: danthegoodman1’s stories are 11/16 (=68.75%) marked as self-authored.)
see my reply to dureuill
This is from January – before the issue that emerged with Wedson. Rust ecosystem indeed seems to have an issue with burnout. And I know I might be wrong, but I can’t help but wonder how much of that has to do with the language itself.
I wonder how much longer conversations around Rust code are versus same typical code in a language like C or Java. Because Rust forces a very strict line of concerns, at every corner, you are not only debating with a counter-part, but you and the counter-part are having a meta-debate with the language and the ecosystem.
In other words, instead of the conversation being like: “What is the right approach to solve this particular problem at hand?” becomes: “What is the right approach to solve this particular problem at hand while accommodating the Rust compiler requirements?”
This property is true of every programming language, you can only write code it allows you to.
I don’t find myself discussing “how do I accommodate the compiler” much when writing Rust code. It’s just a programming language. It’s not that different from any other. And sure, maybe that’s because I’m me, but Google says the same: https://opensource.googleblog.com/2023/06/rust-fact-vs-fiction-5-insights-from-googles-rust-journey-2022.html
This is what I call Rust brain (I used to call it Haskell brain) – that happens when someone can automatically code switch between languages. This is a uncommon gift and, in my opinion, should not be generalized to a large population.
from …
There seem to be selection bias in this post you shared.
I would like to see a bigger and more diverse sample set.
What is the bias, specifically?
Sure, everyone would love a bigger sample, but that’s not bias.
the bias’ name is “selection bias”.
From the link:
There are two selection biases made: one, it only take googlers; and two, googlers who committed rust code. And then, this articles goes on to make general statements. In order to make general statements, they’d have to produce a sample selection that is also general.
For example, for this rumor to be accurate, they would have to change their claim from
to
You can’t learn Rust without committing Rust code. That’s not selection bias. That’s an inherent part of asking this question.
Furthermore, it’s more complex than just “people who authored and committed Rust code.” The group in question was a mixture of people who had Rust experience before, and those who did not. Only 13% had previous Rust experience. That means that 87% of these folks would not have had “Rust brain” before this study kicked off.
I do agree that results outside of Google would be great to have. But I think you’re really stretching here.
I am going to assume that you meant “You can’t learn Rust without writing Rust code”. I agree with this statement.
Not every one that is trying to learn Rust will actually commit Rust code at Google - either by self-selecting into not doing it, or for lacking opportunity of doing it. Even inside Google, for these rumors to be validated, they would have to also look for people who tried and failed - which by the definition of their own sample, they haven’t.
When I claim there is selection bias, I am not implying anyone was dishonest - all I am saying is that good statistics and science is hard. This article fails at that.
I would counter though, that claim that Rust is just another programming language is also a stretch. I like Rust and I’ve been learning it for while, and I can safely say it is a paradigm shift – and not just another programming language, in any sense.
Okay, in theory, someone could have quit their job because they couldn’t learn Rust. I’d find it hard to believe that that’s a large enough percentage of people to meaningfully change the outcome.
excellent point!
Why do you say it’s rare? Plenty of programmers work in multiple languages, which all have their own restrictions and practices.
There’s a whole lot of “blue collar programmers” out there who know only one or perhaps several similar languages (think Java, C# and C++ or PHP and JS). Those aren’t the types who tend to frequent forums like lobsters or the Orange Site, but still they’re most likely the majority.
In my experience, adding constraints on what code compiles makes choosing the right approach simpler, not harder.
I’m just a Rust practitioner, not a language contributor, but I feel like the reasons laid out by the author, that are mostly non technical, suffice to explain a burnout
Wedson obviously burned out because the Linux kernel is a burnout machine.
I think it’s a hole in Rust’s design that user code can’t make types that behave like references. First-class references can be (re)borrowed for
&self, but when you make your own type that is supposed to behave like a reference, it has to beWrapper<'a>, rather than something like&'a Wrapper. Borrowing such newtype creates a double reference&'b Wrapper<'a>and now you have two levels of indirection and two lifetimes.Doesn’t that have more to do with
WrapperbeingCopyor not?If
Wrapperis notCopy(unlike&T), then I can see how reborrowing can be an issue (well, you can do it, but only “one-way”).If
WrapperisCopy, then you can write:Agreed on the general sentiment that “view types” are somewhat second class, though.
EDIT: I think there is a special “reborrowing” behavior for
&mut T, though, because&mut Tis not copy, but still allows “reborrow”Copyor not, it doesn’t help with&selfmethods. Nearly all traits use&self, andselfwould mean something different for types that aren’t a wrapper around a reference.Having to explicitly call
reborrow()instead of&valueor automatic coercion is another example where it shows that newtype wrappers don’t work like first-class reference types.Isn’t something like that in the works for Rust for Linux? For their custom Arc type.
Interesting article. I think variance is unintuitive in Rust because it is so hidden. Ideally, the variance should appear in the lifetime notation. It would allow to express that, e.g., a covariant lifetime is needed for a trait.
Currently, the places where variance cannot be expressed are mostly supposed to be invariant, ergo the issue encountered in the post.
Explicit variance annotations have their own problems. I highly recommend the paper “Taming the Wildcards: Combining Definition- and Use-Site Variance” by Altidor, Huang, and Smaragdakis.
That’s s great link, thank you. I’ve only informed myself about how it works in Rust. But I wonder how other languages deal with it. We might be able to learn from them?
As the paper says, Scala and C# both have definition-site variance. Java has use-site variance.
You can look at Rust’s RFC that implemented inferred variance and also gets into the trade-offs and other designs. https://rust-lang.github.io/rfcs/0738-variance.html
Thank you for the paper and the RFC.
I’m coming to the issue from the perspective of the user of types containing lifetimes, not the implementer of these types.
I argue that inferred variance makes it hard to consume types containing lifetimes and generic parameter, because one needs to check the internals of the structure to know how it behaves regarding variance.
It also indirectly makes the language inexpressive when wanting to talk, e.g., about the kind of types that are covariant on a lifetime (I don’t think you can implement a
is_covariant“type trait” or “where bound”)Even for implementers, it gives raise to compatibility hazards when a struct that should be covariant is accidentally made invariant, or when a struct that ought to be invariant omitted the correct PhantomData to make it so.
I posit that explicit annotations would put us on a path of adding more expressivity for variance, and a better understanding of variance.
Yes, variance is hard. That’s why I think we should make it super visible.
This proposal is interesting in that it maximalizes safety for C++ while minimizing interoperability costs.
I find the difference with Rust<->C++ is instructing to consider:
The list of advantages pretty much stops here. Introducing safety requires rewriting both for rust and safe C++. The stdlib has to be different in both cases. The limitations due to borrow checking will largely overlap.
This sets a clear goal for Rust and C++ interop IMO
Nitpick, but I couldn’t find where signed integer overflow is being discussed in safe C++. Would love to know how this is handled
I doubt they can make changes to the core types, but the quantities and units paper has a very rich set of tools for addressing these problems.
They could specify that signed integer overflow wraps around inside
safefunctionsWrapping is not necessarily safe (though it’s safer than UB). The most common integer overflow vulnerabilities come from multiplying an attacker-provided value with something and having it wrap, so now you have a smaller value than you expected.
It only becomes a memory safety issue if it then serves as an index in a buffer. Safe C++ already guards against these by having checked access in std2
That’s true, but attacker-controlled wrapping values can still cause you to index the wrong (in-bound) element in an array, which may be a reference to a different security context. I’ve seen vulnerabilities from wrapping integers that were not memory safety bugs.
That’s true. I was talking in the context of “safe C++” providing memory safe C++.
To avoid non-memory overflow vulnerabilities, Rust provides
checked_add,saturating_add, andwrapping_add. From a security standpoint I wish the language banned arithmetic operators (or provided multiple ones to concisely express the desired semantics). There’s a clippy lint but it is allowed by default and I might have a hard time proposing to make it a warning in projects I contribute to.I like the Smalltalk default of an integer type that silently promotes to a heap allocated big integer on overflow, but that’s vulnerable to denial of service attacks and truncation problems. It’s quite easy in C++ to define wrapper types for wrapping and saturating arithmetic. Saturating is often a better choice than wrapping since there’s a single value for all cases of overflow.
If an attacker-provided value causes UB, and the compiler has generated code that assumes UB cannot occur, then the attacker can still trigger behaviour that violates the C++ virtual machine.
I don’t think that a “safe” subset of C++ can be truly safe if it has any UB. Memory safety eliminates an important subset of UB but not all of it.
I think you have this wrong: memory safety requires that there is no UB, at least in the C++ sense of UB, since any UB is a violation of memory safety.
This is a common misunderstanding, and sometimes a useful simplification, but compilers do not “generate code that assumes UB cannot occur”. They generate code that satisfies the requirements of the C++ abstract machine, which is trivial in the presence of UB because there are no requirements in that case. “Behaviour that violates the C++ virtual machine” seems to be something of a mixed metaphor or other conflation.
I think that these are equivalent. Since any code is a valid implementation of undefined behaviour, the compiler can generate code that assumes that UB won’t occur. This is optimal and correct.
Of course as the programmer you can’t assume this. But it is a useful equivalence to understand when trying to debug something.
The end result is equivalent, but that does not mean that the two things are the same.
What you have described is the essence of the useful simplification I mentioned. It is often expressed instead as “the compiler assumes that UB can’t occur” (meaning that the compiler was written based on an assumption that UB can’t occur, and generates code based on that assumption) and that’s problematic because some people have trouble comprehending that UB not having defined semantics is satisfied by a code generator that ignores code paths necessarily containing UB, and conditions that imply UB, and claim that (for example) pretending UB doesn’t happen is “unsound” because it certainly can happen.
Hence, I would rather be precise: there is no assumption that UB doesn’t happen, rather, code generation typically does not constrain its output based on code paths or conditions that necessitate UB. The effect in terms of generated code may be the same, but the latter does not imply that compilation is based on a faulty premise, which it is not.
I think both other people in this reply chain agree with that.
I don’t find this article a good defense of exceptions. It has a strong bias towards a specific error handling scheme: “retrieve error at the top level, log it and continue”. This basic scheme hides a lot of the drawbacks of exceptions. Furthermore, it keeps conflating the properties of exceptions and results with things that are independent of these two, but relevant to the languages being considered.
For the initial author of the code, they might. The hidden early returns caused by exceptions will make maintenance harder, though.
Having functions that take result on the entire stack is what you want and will give you the same benefit. This is more about having RAII than exception vs results.
?is no boilerplate. It is a single character, and it is load-bearing. It means that the operations is fallible.No, Rust has panics for this, which has the same as exceptions.
Conflating the decision of aborting on memory allocation failure (motivated by Linux overcommit behavior) with the mechanism used to return the error. Ironically, the “simply hidden” failure mode in the case of arithmetic operations describe a case where Rust would panic, so throw an exception.
This interface is useful in a kernel context where you know that there is no overcommit, and completely useless in an application where the memory will be happily returned to you, and then the OOM killer will kill your process. Anyway, it has no bearing to exception vs result, as
try_newreturns a result, so the point being made is unclear.This paragraph is about the dilemma of putting context in the error type or fetching it from the environment. It applies all the same regardless of whether you’re using exceptions or results.
This is generally mitigated a lot in the presence of inlining. It is also more the characteristics of the current implementations that something that will necessarily remain true.
Rust does, in the form of panics. They do occupy some space in the error-handling story, taking in on some use cases where they are a better fit than Result (e.g. asserts)
Go also has panics, and AFAIK they’re also used in those situations (e.g.: OOM). So yes, while I prefer Exceptions than Errors, I also can’t concur with the author points.
std::regexis notoriously slow and should not be used: https://stackoverflow.com/questions/70583395/why-is-stdregex-notoriously-much-slower-than-other-regular-expression-librarieThe author would have been better off by depending on PCRE2
Or RE2, it’s not the fastest, non-movable objects are annoying, and their use of an in-place Compile instead of a builder is infuriating to me, but it’s reasonably easy to use and the lack of backtracking makes it very attractive as a default.
You’re probably alluding to this with “lack of backtracking” - I’ll point out that the most notable property of RE2 is that it has linear time complexity in the length of the pattern and the length of the string (O(m*n)), making it much less vulnerable to DoS attacks if one or both of them is user-generated input. Russ Cox has a series of articles on this.
That is exactly what I was alluding to yes.
Can’t wait for ATPIT, it’ will fix what has been been an ongoing “hole” in Rust’s expressiveness.
I’m also eagerly waiting for the new trait solver as I understand it is blocking a lot of features and bugfixes
I also have hope that the sandboxed build script can improve the custom build situation, very happy to see some love put there.
Can’t say the same for the work on improved ergonomics for refcounting. From here it looks like a large effort with dubious gain
Had no idea ATPIT was even being worked on. That’ll be a huge improvement.
Very interested and curious about the ergonomic ref counting. I work with a lot of async tasks, so I’m cloning
Arcs everywhere.So do I, and I really find that eliding clones would not especially improve ergonomics.
Especially if it comes with the complexity cost of adding a trait for lightweight clones.
That’s my concern as well. I haven’t ever felt the pain of cloning Arcs to be a significant enough burden to justify the complexity of a lightweight clone, or implicit-cloning even limited only to smart pointer types. The lightweight clone trait seems to have all kinds of downsides from the little bit I’ve seen. And I’d worry about implicit cloning in a general case for obvious reasons (not that this is being considered), or even when limited to ref counting pointers feels not great to special case certain types for such a fundamental trait. If they were going to want syntax-less clones for ref counted pointers it feels like they should have used Copy, but that feels wrong too?
I was originally concerned with implicit clones as well (I can’t remember what the working name for it anymore), but I’ve been convinced it’s probably for the best. I, too, don’t have a problem with cloning things, but I empathize with those that do. I feel like the extra complexity involved in making some sort of simplified/implicit cloning system is worth it; not for the sake of people like us who are willing to put up with Rust’s quirks so we can take advantage of it’s benefits, but for everyone else who isn’t so enamored by the language. Explicitly writing redundant clones everywhere just to pass a thread boundary does look rather silly, that’s hard to deny. A programming language is about encoding intent, not raw machine instructions, otherwise we’d all just program in machine code. Rust’s overt explicitness is a joy, but in instances like this, it feels a little redundant and not particularly worth the effort to maintain that explicitness in this situation. I think for just this one particular issue, this is where we can let Rust “do what other languages do” and hide the cloning behind some convenience syntax. However, I will say I would prefer the “implicit clone” to be somehow explicit, like through an operator, like how
&lets you take a reference.As for making
Arccopyable, the issue there is thatCopycan’t be implemented on types that store anything that isn’tCopy(as far as I know). To manually do that behind the scenes would be atrocious, I’m already uncomfortable with how long it’s taking the core team to makestd::ops::Trystable. If one type can do something (and it’s not a primitive), all types should be able to do it.Yes, if a move closure could basically get Copy-ish semantics and end up with an automatic Clone of things that are appropriately marked, a lot of fluff would disappear in a lot of Arc-heavy threaded code I work with!
Am I using async differently than everyone? The need for explicit
Arccloning has almost entirely disappeared for me with the async/await syntax.Are you using
spawn?Well, you do need to use
spawnto start up new tasks and you need to explicitly cloneArcs ahead of time in order to pass shared data in. I don’t think there’s any way to avoid this at the moment, but I’d love to see an example of sharing data between tasks without it.It’s true that you don’t need to do explicit clones once you’re in the async block for the task. I think the reason why this is worth fixing from an ergonomics point of view is that it’s literally the first thing you do when writing an async Rust program, so it’s an obvious stumbling block for anyone not used to Rust’s “clone the shared pointer before moving it into the closure or async block” pattern.
I use
spawneverywhere, yes. I basically program in an actor paradigm. My primary use of cloningArcis to give each task anArcof the program config, at the very least, but of course I’m also always passing in some other state for them tor read or modify, like some kind of “world” mutex for each task to get a handle to their siblings to communicate with them, if I need that.Although honestly it hasn’t been much of a pain for me, since I simply construct the structs for these “actors” in-place where I spawn them, so any
Arccloning is easily drowned out by the rest of the struct construction.Story time:
Once, I had a colleague come to see me with “a segfault”.
As usual I started debugging it in valgrind and ASAN/UBSAN.
I’m a bit fuzzy on the details, but one of the sanitizers would mysteriously not exhibit the issue, while the other would crash with a very unhelpful message. As for valgrind, it would itself segfault when ran on the code.
The debugger was looking very confused as to the size of an object.
Turned out that my colleague had closed an anonymous namespace too early, in two different files.
He had meant to write:
but he had written:
Another file with the same mistake would declare another
InternalDetailsstruct with a different layout.That a single typo would lose us as much time as 2 afternoons of 2 engineers with no tool able to detect it was infuriating to say the least.
It seems intuitive to me that if you want a graceful shutdown then you need to wait for it to be finished before dropping the runtime.
I must be missing some subtlety here 🫤
Happy all your projects clipped lints being broken on CI for all that celebrate!
If someone is hunting for a project that can have an impact in the rust ecosystem here’s an idea https://ruby.social/@Schneems/112849397318932169
The approach I recommend is using the toolchain which corresponds with the MSRV.
We added a
rust-toolchain.tomlfile at the root of our repository, and we’re using the helix rust actions that take that file into account when no toolchain argument is specified.This way we are not broken when a new Rust version releases.
This is especially vital for us because we release on Mondays and we are working 4-days weeks with Friday off, and in our timezone the Rust Release time is on Thursday afternoon.
We don’t want “the release build is suddenly broken on Monday because a new Rust version released” to be a thing.
We have automation on our repository to create an issue about updating the Rust version every six months. We generally tie it with an update of our dependencies (we also have dependabot for security deps)
This new articles reinforces my thoughts about the design:
Claimshould not be a trait, but an annotation onCloneimplementations, lest it inevitably ends up used in a bound where it shouldn’tCopyfield to your struct won’t guarantee that it remains moveable. It also has weird interactions with future extensions of const generics (think[T; N])The more I read about the design, the more I think that if the gain is just about clone ergonomics and preventing a performance pitfall, then the gain doesn’t outweigh the cost