HNHacker News
TopNewBestAskShowJobs

dureuill

456 karma · joined July 24, 2021

submissionscomments
dureuill··on C++ Papercuts
with const pointers you have the different problem that you need to define what happens when the nullptr is passed.

While I would certainly prefer the reference to be visible at the call site (a mistake Rust did not repeat), if I have to choose, I prefer the inconvenience of having to use an IDE like qtcreator that will display references in a different style, to introducing an invalid value that needs to be handled (and will be mishandled).

So yeah, as a senior dev, it checks out.

dureuill··on A half-hour to learn Rust (2020)
The person you're responding to was specifically responding to your criticism that Rust does not provide a specification that implementations can target.

You're now listing languages, such as Python, that don't have a specification, so it is unclear what your criticism actually is.

By python's standards, Rust is very good: a lot fewer breaking changes (changes are tested against the entire, huge, open source ecosystem to check if they are breaking, I think this level of testing is unparalleled), the release and versioning process is extremely clear (one minor release every six weeks, patch releases to address unexpected regressions + security issues), and it also have a really well specified evolution process through the RFC process.

dureuill··on A half-hour to learn Rust (2020)
> Now can I have just a safe C without all the other stuff, pretty please? :)

Not sure how much we could remove from Rust while keeping the problem tractable. The borrow checker is needed for compile time memory safety sans garbage collection. For borrow checking to be tractable, one needs shared references, exclusive references, owned values, the possibility for reference counting, and the possibility for interior mutability. This mandates smart pointers, that pretty much mandate generics. These various abstractions also mandate a capable standard library, unless the language would hardcode all these abstractions, which would endanger the low-levelness of the language (since you wouldn't be able to implement your own abstractions for e.g. embedded contexts)

For the demarcation between unsafe Rust and safe Rust to work, one needs encapsulation, so field privacy.

For concurrency, the language requires a way to mark types as thread safe, which requires a way to say things about structures, so a least a weak version of traits.

A smaller version of Rust probably exists, but I don't think we could remove as much of the language as we could imagine at first without compromising safety. It also wouldn't be "just a safe C", because C itself has heaps of accidental complexity (e.g. the way function pointers are declared, integer promotions, implicit conversions, the antiquated textual inclusion compilation model that is prone to ODR violations and complicates build systems, array decay, and so on...) that would need to be removed.

dureuill··on A half-hour to learn Rust (2020)
Just to elaborate on the "just more comfortable once you already know it" part, I think a lot of Rust is optimized (willingly or not) for the "once you know it" use case.

Which, while it might arguably hurt adoption, is a good value proposition, since you spend a lot more time knowing the language than not knowing it.

Now, not everything is always perfect, and I agree that the `if let` is not the most useful part of the language, as it drives pointless discussions about when to use it vs match (some people prefer the esthetics of match even when an if let can be used, others prefer to use a if let whenever possible). This redundancy apart, the construct doesn't eat any mental energy once you know it. The same can't be said of many of C++ quirks (initialization rules, member-initializer list in constructors, the rule of five)

dureuill··on A half-hour to learn Rust (2020)
As someone with bad eyesight, Rust has been very legible, and the sigils actually help for that (in the same way as most mathematical notations make formulae easier to parse).

It does take a little while to get accustomed to, but the investment is more than worth it.

Can't comment on my sanity though.

dureuill··on C and C++ prioritize performance over correctness
> , after all, the compiler needs to check for UB to generate code.

No the compiler does not check for UB to generate code. A lot of UB (runtime out of bounds, use after free) are very difficult to detect statically (at least without specialized annotations).

The compiler applies transformations to the code that are valid (in the sense that they produce an equivalent program) only in the absence of UB.

So if the input program contains UB, the transformed program produced by the compiler may or may not be valid

dureuill··on C and C++ prioritize performance over correctness
Yes you can. They're called panics
dureuill··on C and C++ prioritize performance over correctness
Getting generic data structures that are more efficient than his specialized C data structures is exactly what happened to Bryan Cantrill when he ported a carefully optimized C program to naive Rust.

> Yes, you read that correctly: my naive Rust was ~32% faster than my carefully implemented C.[0]

> As a result, this code spends all of its time constantly updating an efficient data structure to be able to make this decision. For the C version, this is a binary search tree (an AVL tree), but Rust (interestingly) doesn’t offer a binary search tree — and it is instead implemented with a BTreeSet, which implements a B-tree. B-trees are common when dealing with on-disk state, where the cost of loading a node contained in a disk block is much, much less than the cost of searching that node for a desired datum, but they are less common as a replacement for an in-memory BST[1]

> So, where does all of this leave us? Certainly, Rust’s foundational data structures perform very well. Indeed, it might be tempting to conclude that, because a significant fraction of the delta here is the difference in data structures (i.e., BST vs. B-tree), the difference in language (i.e., C vs. Rust) doesn’t matter at all.[1]

> Implementing a B-tree this way, however, would be a mess. The value of a B-tree is in the contiguity of nodes — that is, it is the allocation that is a core part of the win of the data structure. I’m sure it isn’t impossible to implement an intrusive B-tree in C, but it would require so much more caller cooperation (and therefore a more complicated and more error-prone interface) that I do imagine that it would have you questioning life choices quite a bit along the way. (After all, a B-tree is a win — but it’s a constant-time win.)[1]

> All of this adds up to the existential win of Rust: powerful abstractions without sacrificing performance.[1]

[0]: http://dtrace.org/blogs/bmc/2018/09/18/falling-in-love-with-...

[1]: http://dtrace.org/blogs/bmc/2018/09/28/the-relative-performa...

dureuill··on A Guide to Undefined Behavior in C and C++ (2010)
> Not to mention that memory safety is just one type of failure in a cornucopia of potential logical bugs.

You can die of multiple illnesses so there's no point in developing treatment for any particular one of them.

> I like core dumps because the failure is immediate, obvious, and fatal.

Core dumps provide a terrible debugging experience, as the failure root cause is often disjoint from the dump itself. Not to mention that core dumps are but one outcome of memory errors, with other funnier outcomes such as data corruption and exploitable vulnerabilities as likely.

Lastly, memory safe languages throw an exception or panic on out of bound access, which can be made as immediate and fatal as core dumps. And much more obvious to debug, since you can trust that the cause starts indeed at the point of failure

dureuill··on Why Rust helps even if you have to use a lot of `unsafe`
> Can you give some examples of those tools?

ASAN (address sanitizer), UBSAN (undefined behaviour sanitizer), Valgrind.

All of these only detect runtime violations, so they can find bugs but don't prove their absence. On top of that I got a sufficient number of false negative or even bugs that wouldn't reproduce/wouldn't give interesting information with these tools that I can confidently tell you they don't even approach the guarantees provided by Rust.

There are also static analyzer like cppcheck and clang tidy. They do find some useful issues, but they:

1. Have false positives 2. Are a hassle to add on top of existing projects 3. Have false negatives, so a code that passes is not necessarily devoid of UB.

> Are smart pointers in C++ that ubiquitous and good that memory corruption is a thing of the past?

Smart pointers in C++ effectively prevent some classes of double free errors and some classes of leaks (like all RAII, I might add). They do very little against use-after-free in the general sense, because as soon as you take an observer pointer/reference (the equivalent of a Rust borrow), you have to either:

1. Forfeit safety by handling its lifetime on your own 2. Forfeit performance by unnecessarily cloning data (and also forfeit correctness in some cases, as the "sea of shared pointers" approach of some codebases create object identity issues and memory cycles).

The standard library is also full of footguns that can cause UB, such as calling `vector::front()` on an empty vector, that come up a lot in practice. I think cppcheck was able to flag some of these but I'm unsure of this off the top of my head.

Lastly C++ has a slew of very ugly errors like ODR violations that do happen in practice (albeit rarely) and that are completely outside the purview of tools (the last one a former colleague wrote would generate completely garbage information in asan, and would cause valgrind itself to segfault. The error cost the afternoon of two engineers for what was a typo causing a namespace to be early closed in a file... The kind of abhorrent error that causes to avoid these languages as much as I can).

I developed in C++11 for almost 10 years, and I am now a professional Rust developer. Productivity, developer experience, and correctness are night and day, Rust's advantage.

dureuill··on C++23: The Next C++ Standard
> It does have a lot of developers saying good words for it whenever there is a chance in recent few years, but the reality is that, it's hard for most developers to learn, because of that it might remain to be a niche language for system programming.

On what basis is this "reality" based? Recent survey of 1000 developers by Google denotes "[the] ramp-up numbers are in line with the time we’ve seen for developers to adopt other languages"[1]

> in fact if you use rule-of-zero, RAII, smart pointers etc correctly your code can be very much memory-safe already.

That's not what memory safe means. Memory safe means that even if you make a mistake, you cannot get a memory error, something that is true of Rust minus the `unsafe` superset. Even with what you describe, you can trivially get UB on C++ if you: keep a reference to an object, use a smart pointer after move (something that the compiler will not warn against), call .front on an empty vector, dereference an iterator that got invalidated by pushing in your vector, use a non thread-safe object in a multithreaded context. Rust will statically prevent a memory error from occurring in any of these situations.

If you're cautious, you may not get memory errors in a C++, but that's not "memory safe".

Also, as a C++ developer turned Rust developer, C++20 does nothing to capture my interest back. The module system is atrocious (orthogonality between modules and namespace, way too complex for what it wants to achieve, module partitions seriously?, poor tool support 3 years after the standard was published), there are no ergonomic sum types and pattern matching, no standard package manager (and the legacy compilation model based on textual inclusion makes build system more complex than they could be), no improvements on thread safety in sight (to be fair, you pretty much need your language to feature a borrow checker and be built around thread safety, it cannot be bolted on). I have little hope C++ is in capacity of addressing these issues in the next 6 years.

[1]: https://opensource.googleblog.com/2023/06/rust-fact-vs-ficti...

dureuill··on Rust fact vs. fiction: 5 Insights from Google's Rust journey in 2022
Thanks for the elaboration. Now I'm wondering if this hasn't something to do with bottom-up people vs top-down people and their natural affinity towards lower level or higher level languages.

I'm very top-down, always starting by sketching the API I'd like to see for the feature, and then filling in the implementation. A lot of people I know from C++ are bottom-up, they toy with the implementation and then go up to the API. I found that what they like in systems programming is being close to the machine, while my interest lies in the OS boundaries and interfaces, not really the hardware.

I'm thinking maybe those different ways of approaching programming make it easier or harder to learn rust.

dureuill··on Rust fact vs. fiction: 5 Insights from Google's Rust journey in 2022
Really? Coming from C++, learning Rust felt very natural and welcome because it put a name and a structure to the mental model I had been using to keep a semblance of sanity while doing C++. Maybe learning primarily with C++11 was a factor?
dureuill··on The Rust I wanted had no future
To be precise, Rust will refuse to compile code that accesses the value without checking that it is not empty.

Rust does that with the combination of "sum types" (aka "enums with data") and pattern matching. An `Option<T>` is either `Some(T)` or `None`. Matching on an option with the pattern `Some(value)` creates a new syntactic scope where the value is accessible. This scope is only entered if the `Option` is actually non-empty.

All ways of getting the value from the option are ultimately shorthand for a match in that way. For example, option.unwrap() will either get the value if it there, or panic. Option.unwrap_or(x) will either get the value of it there, or use x instead.

In practice this is x100 less error prone than C++. Source: was burned by vector.front() on empty vectors more than once. In C++, UB. In rust, usually the "empty case" is considered when first writing the code, or at least caught during review (unwraps tend to be very visible in reviews)

dureuill··on A Linux kernel without struct buffer_head
I decided to take the plunge:-)

As a systems programmer I believe it will have useful content for me, and I want to support the initiative

dureuill··on A Linux kernel without struct buffer_head
Another great write-up on lwn.net.

Really makes me consider subscribing.

More on the topic I'm wondering how the remaining users of buffer_head could possibly migrate? Especially since we're talking filesystems I'm guessing we don't want any change in behaviour as it could result in a loss of data

dureuill··on Valgrind 3.21 Is Out
While one shouldn't submit every software update to hn, I found the discussion about valgrind development interesting: how the scattered online presence with various tools makes contributing harder. I'm wondering how feasible it would be to migrate development over GitHub?

Also one should probably talk about valgrind from time to time, it is such a great tool!

I'd love to contribute, but the described development process is scaring me away :-)

dureuill··on Microsoft exec says Windows 11 kernel will soon be booting with Rust inside
True, but also worse than that: if an unrelated application starts consuming slightly more memory it can trigger an oom kill of your application
dureuill··on Arbitrary code execution during compilation – rust
> When the do_not_compile_this_code is opened in VS Code with the rust-analyzer plugin, the editor expands the some_macro!() macro. This macro reads then content of ~/.ssh/id_rsa_do_not_try_this_at_home and deletes the file.

Is that true though? I think I remember that by default vscode won't enable extensions like rust analyzer when opening a folder, unless you confirm that you trust the code in that folder first. Seems like reading code from the internet to ascertain it is not malevolent is a good use case for not trusting the code.

dureuill··on Testing GPT 4's code-writing capabilities with some real world problems
> And it might be a bit faster, but you are really working for the paycheck that day when the obvious thing is to run the basic A* algorithm twice with different configuration steps.

Pretty much this. Attempt to find a path to the target destination with a first A* run that disregards fire tiles, and if that fails due to limited movement, then do a second run with the fire tiles. I like that this mirrors the decision making a human would follow, too: I won't cross the fire tile unless I'm absolutely required to.

dureuill··on Emitting Safer Rust with C2Rust
From reading the article, I get that the latest version can transform some C into safe Rust.

This gains us machine-proved memory safety. This is huge.

dureuill··on Switching from C++ to Rust
I get the "theoretical" point, but the issues I encountered in practice are closer to the ones I listed in my response than to valueless_by_exception (which I never encountered, I think?).

Wrt type safety we had more pressing issues with C++ (use after move, implicit conversions, ...)

dureuill··on Switching from C++ to Rust
> Variant types where one has to use objects as types don’t come up that often in API design or data structures in my experience with mobile and system programming on e.g. Linux

You can't use what you don't have, so you adapt to the tools you do have. In my C++ time, the team would often write types that logically held several variants. However they were expressed as product types, so with space overhead and error-prone, unergonomic use.

Sum types are game-changing. Look at any Rust project you'll find enums with data everywhere. They're just a building block to model problems, like product types are. A language that miss them is as strange too me as a language without product types. After 10 years of mostly C++11 and C++14 I would never go back to it for this reason alone (although as outlined in the article there are other reasons too).

> Error types are probably the most popular incarnation of that. There’s several libraries available and they will be part of the C++ standard.

Again, with what performance and ergonomics?

dureuill··on Switching from C++ to Rust
Yes, but:

- nobody uses it in the ecosystem. As outlined in the article, a lot of value of Option/Result is derived from their pervasiveness in the Rust ecosystem. C++ is far from this

- the ergonomics of it are terrible: no pattern matching, structural variants instead of named variants (yes you can emulate that with wrapper types but meh), lambda-oriented matching means you cannot as easily do things like early returns, statement-oriented language limits the usefulness anyway, lack of combinators, and for error handling specifically, lack of `?` operator

- performance is dubious. I had very steep and unexpected performance cliffs when lambda inlining started to fail for some reason. Having sum types be a language construct guarantees we're not relying on things like lambda optimisation here.

dureuill··on Zig as an alternative to writing unsafe Rust
> Do you have an illustrative example?

Illustrative, I don't know, but I'll try to give more context.

When writing a library, it is important that public items (like functions and enum) don't change between minor versions so that client code doesn't need to update their calls to the library.

Sometimes when refactoring code you end up modifying how a library function is implemented. Maybe it will now depend on some file being present on the system, while previously it wouldn't, meaning that the absence of that file adds a new error variant to this function.

In today's Rust, since the Error type of a Result is an explicit part of a function's signature, such a change is very noisy to the library's maintainer: it entails either modifying the signature of the public function to return a different error type, or modifying the Error type itself, which is also public.

When this happens, the change needs to be reconsidered: either you can defer it to later, provide an additional function with that new implementation and error variant, try to make it work with the error types you already have, or decide in that it actually warrants a major version bump, in conscience.

By contrast, if the set of errors of a function is inferred rather than part of its explicit signature, it means that modifying the implementation you can add a new variant without even realising it (for instance, by mixing the variant name with a variant returned by a sibling function that you thought was already used by this function) and break semver in a much more silent way.

I guess it also makes life harder for tooling, since it has to parse the implementation of a function (and all its subfunctions) to rebuild the set of errors, as opposed to simply parse the signature of the top-level function.

> Essentially it compiles to the same as C function that returns an `int` representing the error

That feels very limiting, I often use error types to e.g., attach data about the error. Is there a more general mechanism for sum types for when this shorthand doesn't apply?

dureuill··on Zig as an alternative to writing unsafe Rust
Ah interesting approach, thx for the answer.

1. Doesn't that risk introducing accidental breaking changes by adding a new error to the set in the implementation, since the set of errors is inferred from the implementation? Having a compile error in this case in Rust is often the last barrier standing between me and an accidental major semver bump (since callers have to exhaustively match on the error conditions) 2. Can you have data in the variants of the error enumeration?

dureuill··on Zig as an alternative to writing unsafe Rust
i'm curious, how do you specify the error type in the zig version?
dureuill··on Rust: A Unique Perspective (2019)
"variable" seems untypical for rust. We have "bindings", which are not 100% the same as "variables" (hence maybe GP's feeling of something odd).

Value is OK.

dureuill··on Meilisearch 1.0 – Open-source search engine built in Rust
Hello!

> A quick question, are there any limits around number of separate indexes we can have with meilisearch?

Yes! In v1.0, about 180 indexes under Linux in the same instance[1]. The good news is that I'm personally working on lifting this limitation for v1.1 (planned to release in the beginning of April), which should be able to accommodate an unlimited number of indexes[2] (disk space permits, of course).

Note that having many indexes does have an impact on performance and will keep doing so even after v1.1.

> Or is there a better way to do this.

If it works for your use case, you can try using a single index (or a few indexes) with tenant tokens[3] for multitenancy.

Hope this helps :-)

[1]: https://docs.meilisearch.com/learn/advanced/known_limitation...

[2]: https://github.com/meilisearch/meilisearch/issues/3382

[3]: https://docs.meilisearch.com/learn/security/tenant_tokens.ht...

dureuill··on Can sanitizers find the two bugs I wrote in C++?
I don't follow. What should one use to have safe and efficient dynamically growable arrays in modern C++ if `std::vector` is not considered as modern?
← PreviousPage 3 of 7Next →