|
|
Log in / Subscribe / Register

Vetting the cargo

Vetting the cargo

Posted Jun 12, 2022 15:17 UTC (Sun) by khim (subscriber, #9252)
In reply to: Vetting the cargo by wtarreau
Parent article: Vetting the cargo

> The world is getting more and more concerning: "developer" ... "left-padding" ... "challenging functionality".

If something like strlen could be challenging functionality (and it can, just look on version from glibc), then why couldn't leftpad be problematic?

This being said I sincerely hope Rust doesn't have anything similar to leftpad crate: while I can agree that leftpad may be nontrivial enough to live in some public crate I sincerely hope it would be in a crate with it's friends rightpad, centerpad and other such functions.

Complexity has to live somewhere: if, instead of having one, single, monster libc library you reduce all your crates to single-function ones then you get thousands and tens of thousands of crates as your dependencies which now makes management of crates it's own problem.


to post comments

Vetting the cargo

Posted Jun 12, 2022 15:52 UTC (Sun) by mpr22 (subscriber, #60784) [Link] (3 responses)

That architecture-specific assembly language implementation which is aggressively optimized for performance on large strings rather than readability or ease of implementation was, I am sure, quite challenging to implement, but this falls into the realm of "accidental challenge arising from the chosen method of implementation" rather than "essential challenge arising from the specification".

A naïve compliant implementation of strlen() in native C, on the other hand, is in the realms of "too trivial to copyright":

size_t strlen(const char *s) {
size_t len;
for (len = 0u; s[len]; ++len) {
/* do nothing */
}
return len;
}

Vetting the cargo

Posted Jun 12, 2022 16:41 UTC (Sun) by adobriyan (guest, #30858) [Link] (2 responses)

Funnily enough gcc 12 will optimise this exact code back to strlen():

f:
cmp BYTE PTR [rdi], 0
je .L3
sub rsp, 8
add rdi, 1
call strlen
add rsp, 8
add rax, 1
ret
.L3:
xor eax, eax
ret

Vetting the cargo

Posted Jun 12, 2022 20:54 UTC (Sun) by tialaramex (subscriber, #21167) [Link]

Idiom recognition, there's a lot of this in modern compilers, but it isn't actually new, it was just less necessary in early C compilers than it had been in languages like APL.

Turns out we should let the machine do the boring tedious work, who knew?

Vetting the cargo

Posted Jun 13, 2022 14:28 UTC (Mon) by mathstuf (subscriber, #69389) [Link]

> Funnily enough gcc 12 will optimise this exact code back to strlen():

FWIW, this can only be done because it assumes `s[len]` means `s != NULL` since such things would be UB (and can therefore be assumed to not happen). Otherwise, `strlen` adds the "must not be `NULL`" precondition and is not eligible for such optimization.

Vetting the cargo

Posted Jun 12, 2022 16:32 UTC (Sun) by fratti (subscriber, #105722) [Link] (26 responses)

> This being said I sincerely hope Rust doesn't have anything similar to leftpad crate: while I can agree that leftpad may be nontrivial enough to live in some public crate I sincerely hope it would be in a crate with it's friends rightpad, centerpad and other such functions.

Haha, yeah, about that...

I seem to recall someone having made a "leftpad index" for Rust crates, which counted number of lines in relation to how often the crate is downloaded. There are a few very leftpad-y crates. In general, Rust unfortunately follows the npm microdependencies approach, which does not only have security implications (trusting 30 random personal GitHub accounts to not get compromised/be malicious for what could be one regular-sized dependency) but also means nothing ever has a consistent API or release schedule.

An example for a classic leftpad-ish crate that should really be part of the stdlib or a generic terminal library but isn't is "atty", which appears to literally just be isatty() called from Rust, with some Windows and webassembly compatibility code. Its implementation, including unit tests and comments, is 210 lines. However, naturally, it has a sponsor button, 17 releases, and lives in someone's personal GitHub repository. 570 other crates depend on it.

Stuff like this not just being part of the standard library is what has stopped me from using Rust so far.

Vetting the cargo

Posted Jun 13, 2022 1:45 UTC (Mon) by khim (subscriber, #9252) [Link] (25 responses)

> An example for a classic leftpad-ish crate that should really be part of the stdlib or a generic terminal library but isn't is "atty", which appears to literally just be isatty() called from Rust, with some Windows and webassembly compatibility code.

Why do you think it should be part of stdlib or a generic terminal library? This crate (with crazy hacks like attempt to see if filename includes words “msys” and “pty”) is definitely not what I would want in the “standard library” yet it's perfectly justified if the only thing you want to do is to show color output on the console.

It's actually not that hard to support ANSI colors on most popular OSes (you just need to call SetConsoleMode on Windows and ignore the result: msys/cygwin would handle these in their own way and Windows console would work), but actual detection of whether you are dealing with TTY or not is complicated and unreliable.

Yes, our world is crazy. Deal with it. If crate is 210 lines long but have history of 17 versions and few dozens of commits I would assume it's complex enough to live in it's own crate.

> Stuff like this not just being part of the standard library is what has stopped me from using Rust so far.

Feel free to pretend that Linux is the only OS that matters. Certain languages (Google's Go, cough) do that and are popular enough, I guess. They tend to be confined to certain niches (command-line only utilities, no GUI, etc), don't support more exotic and/or rare platforms, but as long as you only need to work with these they are more than adequate.

For the rest of us, who have to deal with more complicated parts of the world Rust is better choice. Witch crazy “atty” crates and cargo vetting.

Vetting the cargo

Posted Jun 13, 2022 8:26 UTC (Mon) by farnz (subscriber, #17727) [Link] (21 responses)

The issue with Rust right now is that there's no easy entry route to finding the set of crates that make up an expanded "standard" library; community knowledge says that (for example) serde is the right choice for general serialization and deserialization, and clap is the right choice for CLI argument parsing, but even lib.rs doesn't make it easy to solve the problem.

What's needed is someone putting a lot of work in to construct a list of things that would be in a "batteries included" standard library, such that there's no choices left for you, but linking to something like lib.rs when there's no "obvious" choice.

Vetting the cargo

Posted Jun 13, 2022 9:10 UTC (Mon) by taladar (subscriber, #68407) [Link] (20 responses)

What is so difficult about searching for "serialization" or "argument parser" on lib.rs and picking the one with literally orders of magnitude more downloads than any other?

Just look at e.g. Python and see where this "put everything in the standard library" approach leads. You have the very same problem, only now you have half a dozen "solutions" to any given problem in the standard library but the community wisdom says not to use any of them.

Vetting the cargo

Posted Jun 13, 2022 9:48 UTC (Mon) by roc (subscriber, #30627) [Link] (4 responses)

I agree that Rust's approach is better than a "batteries-included standard library", but I also agree with farnz that it would be really useful to have a curated list of "preferred crate for task X".

Vetting the cargo

Posted Jun 13, 2022 11:44 UTC (Mon) by excors (subscriber, #95769) [Link] (3 responses)

There is a curated list of crates at https://github.com/rust-unofficial/awesome-rust which I've found a few useful pointers from, though I'm not sure how good it is in general. (It doesn't even list serde explicitly, just some serde-related crates. It does list clap, but alongside another CLI library that only has 50K downloads in 2 years and was added to the list by its author and probably doesn't deserve to be there.)

I guess you can get better recommendations by asking on various forums or chat rooms, but it would be nice if that community knowledge was distilled into a more easily accessible location.

Vetting the cargo

Posted Jun 14, 2022 22:18 UTC (Tue) by Wol (subscriber, #4433) [Link] (2 responses)

It would be nice if the language writers had a "library of batteries" that is regularly maintained.

So you would have a "recommended battery for strings", "recommended battery for arbitrary precision maths", etc etc. And batteries could be maintained, deprecated, whatever. Especially as Rust has this version stuff, you could easily continue using old batteries, but - importantly - you would know they were old batteries and not actively maintained.

And if you want to deprecate and get rid of an old battery, provided the functionality has been updated, you could create your new battery and update the old battery to be - as far as possible - just a shim for the new one.

Cheers,
Wol

Vetting the cargo

Posted Jun 15, 2022 8:49 UTC (Wed) by khim (subscriber, #9252) [Link] (1 responses)

> And if you want to deprecate and get rid of an old battery, provided the functionality has been updated, you could create your new battery and update the old battery to be - as far as possible - just a shim for the new one.

That's, actually, where you idea falls apart.

Maintaining compatibility is hard.

The deprecation of old crates and replacement with a new ones usually happen when the desired functionality is impossible to add in the backward-compatible way. Otherwise old crates are just extended.

Which more-or-less automatically makes these shims impractical: they would either be impossible or just simple larger and more error-prone than the whole old library.

That's why Linux kernel have “no stable internal API” policy and why Rust keeps it's standard library as small as possible.

As for “list of currently recommended crates”… this idea is discussed so often, that yes, I hope eventually something like that would be made. Maybe with the use of hyped cargo vet.

Vetting the cargo

Posted Jun 23, 2022 9:33 UTC (Thu) by Vipketsh (guest, #134480) [Link]

> Maintaining compatibility is hard.

Don't disagree here.

> The deprecation of old crates and replacement with a new ones usually happen when the desired functionality is impossible to add in the backward-compatible way.

In my experience statements to this effect are, for the most part, little more than mumbo-jumbo that people pushing for breakage use. The reality, again in my experience, is that backwards incompatible changes occur when some developer or group thereof decides that some new way of doing things looks more "pretty" than the current way so they make the change and then substantiate it with the above. I don't rule out that the average Rust developer is honest and hard working such that whenever faced with an issue they do their utmost to attempt to not break backward compatibility and my experience does not hold, but that is counter to everything I've seen thus I'm staying sceptical.

I would also like to point out the measure for "backwards-compatibility" has to be something like:
1, The overwhelming majority of your users don't notice
2, Those who do notice, notice small things and very rarely

Otherwise we end up with the absolutist argument of "every bug may be depended upon by someone, thus *any* change (no matter how straight-forward) may break something thus we wouldn't be able to change anything thus there is no backwards compatibility thus we can change everything willy-nilly".

Vetting the cargo

Posted Jun 13, 2022 12:26 UTC (Mon) by farnz (subscriber, #17727) [Link] (14 responses)

Because a new user has no way to distinguish "the state of the art is clap, and it should be your default assumption" from "everyone uses clap for legacy reasons, but you should be moving on to new-thing". For example, there was a time period in 2020 during which failure would have been the answer you'd find by your heuristic, but everyone was recommending that you move away from failure (no longer maintained) to anyhow and thiserror. Going back earlier on the graph, in 2018, you'd probably have picked up error-chain, even though it had been abandoned in favour of failure; now, there are special cases in which miette or eyre is what you want, and a human needs to tell you to default to anyhow and thiserror even though miette and eyre exist and are growing.

Hence the need for something with human curation - you want something that says "for error handling, your go-to should be thiserror in a library or anyhow in an application", but also says "for an async web server, there are multiple good choices - use the Are we web yet? Web Frameworks page to make your decision". This gets you the benefits of a big standard library when there's a good default choice to make (like anyhow or thiserror), allows the ecosystem to evolve and (importantly!) guides people to move on when something new replaces the old "good default choice".

The trouble with a standard library is that it's ossified - you can't change it easily. The benefit is that it's a human-curated set of "good default choices" for ways to do things. Keeping the standard library small is absolutely the right decision for Rust with its backwards compatibility goals, but means that you need to look elsewhere for the human-curated set of "good default choices" - and it needs the human curation to avoid the problem of getting stuck on a bad default that a standard library has.

Vetting the cargo

Posted Jun 13, 2022 13:27 UTC (Mon) by Wol (subscriber, #4433) [Link] (3 responses)

And your example of "eire", "failure", "error-chain" etc points out a failure even of a curated list - HOW MANY PEOPLE PUT A DATE ON THEIR RECOMMENDATIONS.

I don't claim to be a paragon of virtue, but whenever I update the raid wiki I try to remember to put a date against what I'm doing - not in the logs but slap-bang in the middle of the article or whatever, so anybody reading it is given a big clue as to whether it's up-to-date or further research is warranted.

Cheers,
Wol

Vetting the cargo

Posted Jun 17, 2022 12:20 UTC (Fri) by nix (subscriber, #2304) [Link] (2 responses)

> I don't claim to be a paragon of virtue, but whenever I update the raid wiki I try to remember to put a date against what I'm doing - not in the logs but slap-bang in the middle of the article or whatever, so anybody reading it is given a big clue as to whether it's up-to-date or further research is warranted.

And this has downsides too: if it's not metadata, eventually the article turns into a morass of dates and modified-by-this-person notes, because nobody making a change later knows whether their edits have obsoleted *everything* some other date note related to, so they tend to stay around... (I've seen this in its extreme form in 30-year-old codebases that were religiously maintained like this. It's *awful*. /* DCH 1990-04-14 */ /* LMH 1991-02-22 */ *everywhere*, many lines having multiple notes and hardly any having none. This is what a version control system is for, guys.)

Vetting the cargo

Posted Jun 18, 2022 18:55 UTC (Sat) by Wol (subscriber, #4433) [Link]

I get that.

But when updating the wiki, I generally completely revamped each article (and move the out-of-date version to an archive page).

It takes discipline, but I try and treat it like a "revised and updated edition" - leave the old version for people running out-of-date LTS systems, and the new up-to-date version for those on the bleeding edge :-)

I've dealt with a few too many horror stories of people finding out-of-date pages and almost trashing their systems - fortunately in my experience people tend to ask for help before doing something stupid, but not always ...

Cheers,
Wol

Vetting the cargo

Posted Jun 27, 2022 17:48 UTC (Mon) by sammythesnake (guest, #17693) [Link]

I remember seeing an extension (for Firefox?) that would shade the background of the text on Wikipedia to indicate how recently a particular bit had been updated.

I thimk the authors were assuming new edits might be wiki vandalism that hasn't been spotted yet, but with known authors, you could invert the logic.

Perhaps a more sophisticated iteration could use colours that somehow represent both age and the authors' demonstrated reliability (based on how many edits they've made / over how long a period / what proportion of them get reverted etc.)

A naïve idea might have the strength of the shading represent age and the hue represent some trust score (amber for newbie, green for OG editor, red for somebody with questionable performance, shades in between accordingly) Obviously, these actual colours would be awful for somebody red/green colour blind, but somebody better informed on such things could suggest a kinder palette:-P

Vetting the cargo

Posted Jun 23, 2022 9:42 UTC (Thu) by Vipketsh (guest, #134480) [Link] (9 responses)

> The trouble with a standard library is that it's ossified - you can't change it easily. The benefit is that it's a human-curated set of "good default choices" for ways to do things. Keeping the standard library small is absolutely the right decision for Rust with its backwards compatibility goals, but means that you need to look elsewhere for the human-curated set of "good default choices" - and it needs the human curation to avoid the problem of getting stuck on a bad default that a standard library has.

Way to go passing the buck. Nothing magical happens when the adjective "standard" is attached to "library": if you have to depend on a library which then goes away or changes in an incompatible way you are the same screwed irrespective of whether it was "standard" or not.

Translated the Rust tactic seems to be*:
1, Divide your users amongst different libraries (implementations)
2, All those libraries will inevitably be replaced or change in an incompatible way (by design is the implication I'm getting from the parent comment)
3, Since it is not "Rust" that is changing Rust bears no responsibility (thus Rust always has good backwards compatibility)
4, It is actually the user's fault for "choosing the wrong library" (though no information is given on what should have been used)
5, Since the users are divided there are never enough voices for the problem to be heard

The end result is that everyone ends up just as screwed as if all this functionality was in a (changing) standard library.

*: I doubt that it is intentional by the Rust team, but that doesn't change the end result the way I see it.

Vetting the cargo

Posted Jun 23, 2022 10:57 UTC (Thu) by mathstuf (subscriber, #69389) [Link] (1 responses)

The lines up pretty well with Linux's "stable userspace ABI" and the mess of the userland ABI story above the absolute basics (glibc, X11, and a handful of others).

Such reimpls have happened before (cf. libjpeg-turbo). I think the difference is that the ecosystem has a lot more room to move than the guarantees of the stdlib because of the "if it works today, it'll work tomorrow" mindset. And yes, your crate will continue to work because the old crates are still accessible but you might not have updates.

The Rust internals Discourse definitely catches features which lay out semver hazards (or at least I try to point them out) so that library authors are not caught unaware when they make some otherwise innocuous change that their major number needs bumped. However, it isn't really possible to wrap everything in bubblewrap.

Vetting the cargo

Posted Jun 23, 2022 13:14 UTC (Thu) by Wol (subscriber, #4433) [Link]

> The lines up pretty well with Linux's "stable userspace ABI" and the mess of the userland ABI story above the absolute basics (glibc, X11, and a handful of others).

The obvious (and quite likely broken) way for linux to deal with this is to refactor the obsolete ABI into a module. Said module then gets (mostly) abandoned, and dropped when nobody notices.

Of course, the problem with this is when it's core functionality that can't (practicably) be modularised.

Cheers,
Wol

Vetting the cargo

Posted Jun 23, 2022 13:27 UTC (Thu) by farnz (subscriber, #17727) [Link] (6 responses)

Your conclusion doesn't follow from your argument.

The standard library cannot change in a backwards-incompatible fashion; this is simply not permitted under any circumstances. You can add it it, and you can fix bugs, but you can't break existing users. Further, as you update your language environment, you will get newer implementations of the standard library; one of the defining special cases about the standard library is that the compiler is allowed to assume that the standard library is present (which is why Rust currently has three standard libraries - core, which is things the compiler cannot function without, alloc, which is all of core, plus the things that the compiler needs when the heap is available, and std, which is the full-fat standard library when running in userspace on a normal OS).

This means we cannot remove anything from the standard library, ever. No matter how big a mistake it is, it has to remain present in the standard library forever, acting as a foot-gun for new users (see gets in C, for example), because removing it will break backwards compatibility. And because Rust documents all of its standard library, that means that it's visible to new users whether we want them to use it or not.

It's worth noting, too, that just because it's in an external crate doesn't mean it stops working; you can still use error-chain or failure, and the backwards compatibility commitment of the compiler and standard library means that they will still work into the future. They're just not being improved over time, and the ecosystem as a whole is no longer using them in new projects.

Vetting the cargo

Posted Jun 23, 2022 14:16 UTC (Thu) by mathstuf (subscriber, #69389) [Link]

Note that Rust does have one handle to pull back an API: unsoundness. If an API cannot be used soundly, it is eligible for deprecation and eventual removal (this may involve an edition change) because an API just cannot be used properly. For example `std::mem::uninitialized()` is off limits because `let b: bool = std::mem::uninitialized();` is instant UB because a `bool` may only have a binary value of 1 or 0 (that is, it may appear to be neither `true` nor `false` with some other bit pattern). Now there is the `std::mem::MaybeUninit` type to handle the use case.

Vetting the cargo

Posted Jun 23, 2022 15:40 UTC (Thu) by Wol (subscriber, #4433) [Link] (4 responses)

> This means we cannot remove anything from the standard library, ever. No matter how big a mistake it is, it has to remain present in the standard library forever, acting as a foot-gun for new users (see gets in C, for example)

Then create a fourth standard library - TheFjords. All your footguns go in that library, and the linker complains loudly when it has to pull it in.

Users won't notice (unless they look), but developers will see it, wonder what the hell is going on (especially if cluesers complain it's there), and will hopefully eliminate it from their code.

So we have an effective method for removing dead/dangerous calls from actively maintained code. If the code isn't maintained then there's not a lot anyone can do, and that's true of all ecosystems ...

Cheers,
Wol

Vetting the cargo

Posted Jun 23, 2022 18:13 UTC (Thu) by farnz (subscriber, #17727) [Link] (3 responses)

The library name is part of the import path used in code. If something moves from (say) core to alloc, code that is written for just core (and does not have alloc or std) is broken and stops compiling. Because Rust does not want to cause working code to break just because you update to a new compiler, code can only move one way - from std to alloc to core - and even then, only because std re-exports everything from alloc and core in the places it "ought" to be in if there was only std, and alloc re-exports everything from core in the places it "ought" to be in if there was only core.

So a "TheFjords" standard library isn't helpful - it just means that it's an obvious fix for things that have been broken by the change, but there's not supposed to be breakage to begin with. Putting things in external crates that get deprecated is helpful - nothing breaks if you still use error-chain, but you're making more work for yourself than if you use thiserror and anyhow, and you can do that refactor at a time to suit you - rather than being forced to do it because you're trying to compile for a new architecture and need a new compiler that supports the new architecture.

Vetting the cargo

Posted Jun 23, 2022 23:18 UTC (Thu) by Wol (subscriber, #4433) [Link] (2 responses)

But that's a "break on build", no? So it'll break FOR A DEVELOPER?

What we don't want is for a system upgrade to break a working program. But if a system upgrade means you can no longer re-build a program without being forced to add TheFjords to the build process, then sorry. That's exactly what I want to achieve!

It's a (reasonably) simple fix, but it places developers on notice that there are problems. So my takeaway from what you say is that "a working binary will continue working, but if it has obsolete components it will no longer build". So if it's unmaintained the user isn't affected, and if is maintained the maintainer has to fix it ...

Cheers,
Wol

Vetting the cargo

Posted Jun 24, 2022 0:47 UTC (Fri) by mpr22 (subscriber, #60784) [Link]

> So it'll break FOR A DEVELOPER?

No, it will break for any person building the program.

You don't need to be a Rust developer to build a Rust program.

Vetting the cargo

Posted Jun 24, 2022 10:04 UTC (Fri) by farnz (subscriber, #17727) [Link]

I prefer the experience offered by RustSec - if you don't care about it, don't run the tool. If you do care, run the tool (cargo audit), and things like this deprecation warning will appear and tell you that you've got a risky dependency and why. If the crate comes back into maintenance, then the advisory gets updated to not trigger on new versions; similarly if recommended replacements get added, it'll be updated to tell you about them (when I first looked at RUSTSEC-2020-0036, it did not mention fehler, which has been added to the list).

This is something that's easier to do with external crates than with the standard library, because all the tooling already exists to do it, and you just don't run it if you don't care about it - a user who just wants to build the version of a program they had 5 years ago to re-generate some results can do that - while a developer who cares about security can run the tools and find out if they need to do anything (and if so, what).

Vetting the cargo

Posted Jun 13, 2022 19:38 UTC (Mon) by fratti (subscriber, #105722) [Link] (2 responses)

Okay, so you don't want isatty() in a standard library because its current non-standard-library implementation is bad, but you happily will rely on it for everything that parses command line arguments even on platforms where POSIX isatty() would be enough because clap is the recommended crate for this and pulls it in. Gotcha.

Vetting the cargo

Posted Jun 14, 2022 14:53 UTC (Tue) by khim (subscriber, #9252) [Link]

Yes. If I use library for the firmware development then I don't want to ever deal with then fact that isatty is not implementable there in principle, but then I wouldn't need command-line parsing or clap there either.

And if I develop “normal” app and only care about “usual suspects” (GNU/Linux, macOS, Windows) then I would pull clap and that mess with “msys” and “pty”.

May not be an ideal solution, but from practical POV it's the best I can expect.

Maybe, just maybe, having that mess as part of clap would be better… since I couldn't imagine an app which needs to know if it needs to enable colors or not and yet doesn't parse the command line… but probably not by much.

Vetting the cargo

Posted Jun 14, 2022 16:18 UTC (Tue) by excors (subscriber, #95769) [Link]

atty is an optional dependency of clap, so it's only pulled in if you enable the "color" feature (which is enabled by default, but if you care about trusting dependencies then I think you should always be disabling default features and only choosing the ones you really need).

With default-features=false, clap has 7 transitive dependencies (and one of those is owned by the clap project), which doesn't sound like a crazy amount to be vetted. The default features add another 4 transitive dependencies. But if you turn on all the stable optional features, it goes up to about 28, which does sound more like a crazy amount.


Copyright © 2026, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds