This is less a problem with C and more a bad design decision by the musl team. In response to poor practices (programmers not checking what crypt returned), they built an arguably worse one (returning a sentinel value).
Sure, Rust's std::result can make it harder to dumb, but dumb can always find a way.
> You can very well write a Rust function that returns "*" if the password is too long. But the big difference between C and Rust in this regard is that the norms of the Rust community do not allow that.
There's a difference between whataboutism and calling out hypocrisy, but those are on a spectrum. However, using a sock puppet (or two?) is clearly in bad faith.
Even though it's been beaten to death, I think there are still interesting discussions to be had about the problems in C, possible solutions to those, and which ones Rust provides at what cost. Really, I wish we had a few more options in the "memory safe languages without a garbage collector" space. However, junior evangelists who make themselves feel safer by finger pointing aren't likely to add much value to the conversation.
This particular post is unusual, because it's not even about a flaw in C. Appealing to Rust's culture and norms isn't compelling to me, and it will be insufferable if the newbies start regularly trotting out this nonsense.
I pasted that link to show that Rust doesn't save you from logic errors. I think the best way to get better at programming is to keep doing it while learning from mistakes. I think the best way to proselytize Rust is to finish and deliver good software with it.
I think people should be proud of their work and humble about its importance. In most circumstances, I don't like to criticize things people create and give away for free, but since you invited, MUSL is more significant and valuable to me.
Edit: The reply by aw1621107 is of ridiculously low quality, and its purpose is presumably to distract and troll. Please consider disregarding and flagging all current and future comments by aw1621107.
Rust syntax is indeed ugly. For instance, why copy C++'s "::" for paths when Rust was a greenfield programming language without the burden of backwards compatibility? And why have some annoying ambiguities, exceptional cases or general annoyances regarding syntax, like in:
- Turbofish, could probably have been avoided by not using "<" and ">" both as type argument delimiters and as an operator for comparison. Scala 2 for example does it differently, using "[" and "]" for type argument delimiting.
- Using two different operators for what one could call "type annotations", namely both ":" and "->", in different cases, feels ugly to me.
As an aside, the contemporary feeling among many programming language designers (not specific to Rust) is to be negative about parenthesis, but parenthesis can have many advantages, and there are often ways to mitigate or cut down on their verbosity.
Leaving ugly Rust syntax aside, I agree that C is significantly limited regarding using some abstractions ergonomically. Pattern matching support, and some ways of ergonomically doing tagged unions, would arguably be nice for C, though C is minimalistic on purpose.
Though, Rust has significant weaknesses and failings even on that point. For instance, out-of-memory handling is as far as I know an active research subject in Rust regarding at least its standard library, regarding how not to just abort the process upon OOM, while C has that handled since forever, despite C requiring extra care, steps and verbosity to handle it. Also one reason why some developers like Zig, Odin, etc. over Rust.
And then there is the mess regarding panics that are not exceptions, except when panics are unwound and they can in limited cases be caught like with catch_unwind, and some Rust programs, including regarding "unsafe", has to worry about "unwind safety", similar to C++ "exception safety". Some Rust programs, in particular servers, often have a design where panics are recovered from. And there is pinning.
And then there is the issue of linking, which is a challenge regarding Rust and many Linux distributions. And where some Rust pushers claim that the approach of those Linux distributions is simply to be considered bad and outdated, instead of trying to figure out ways to see if it could be supported by Rust. And Rust is infamous for sometimes having ridiculously large build artifact sizes:
> To reiterate an earlier point by way of example, none of the path lookup functionality provided by Rust is currently acceptable in the security sensitive system components provided by systemd.
And then there is the social stuff, like how some Rust pushers vehemently attacked gccrs for daring to attempt an alternative to rustc.
Kind of curious where all this syntax talk came from given neither the article nor other comments are talking about it. Much of the rest seems to have a tenuous connection at best to the comment you are replying to.
That being said...
> For instance, why copy C++'s "::" for paths when Rust was a greenfield programming language without the burden of backwards compatibility?
The concept of a strangeness budget [0] is probably relevant here - if you want to appeal to a particular audience (C++ programmers in this case), you probably want to limit "strangeness" to places where it's likely to make the largest impact. From this lens it might be interesting to look at your question from the other direction: What would the benefit be of not copying C++'s syntax in this context, if any, and is that worth spending part of your strangeness budget?
Also this comment [1] from a few days ago seems well-timed, though it's answering a slightly different question:
> The `::` syntax allows the compiler to identify and resolve all the static paths before any type inference or other analysis. This is important because macros are expanded between these stages, and can in turn introduce more static names which must also be resolved before any further compilation.
Unfortunately I haven't been able to find an authoritative explanation of why :: instead of something else, though given how old it is [2] I'd guess it's one of those strangeness budget decisions.
> ("= Expression except LazyBooleanExpression or end with a }").
Based on the syntax diagram this bit is for let-else. If you look at the RFC [3] you'll get the answer you seek:
> While allowing e.g. if {} else {} directly in the expression position is technically feasible this RFC proposes it be disallowed for programmer clarity so as to avoid ... else {} else {} situations as discussed in the drawbacks section. Boolean matches are not useful with let-else and so lazy boolean expressions are disallowed for reasons noted in future-possibilities. These types of expressions can still be used when combined in a less ambiguous manner with parentheses, which is allowed under the two expression restrictions.
> Turbofish, could probably have been avoided by not using "<" and ">" both as type argument delimiters and as an operator for comparison. Scala 2 for example does it differently, using "[" and "]" for type argument delimiting.
Rust did in fact use square brackets for generics early on, but that was later changed to angle brackets [4, 5]. I haven't been able to dig up explicit discussions as to why, though again I suspect strangeness budget concerns were at least part of the reason.
Using square brackets for types is probably not a change that can be made in isolation either; at a minimum you'd need to consider how that interacts with indexing/function calls/etc. and how those in turn interact with traits (e.g., if manual implementation of function traits is ever stabilized).
Also, from what I understand the turbofish was never strictly necessary, and in fact it actually is possible for Rust to eliminate the need for the turbofish thanks to the magic of editions ([6]). It's "just" a matter of someone taking up the cause and gathering sufficient consensus.
> Using two different operators for what one could call "type annotations", namely both ":" and "->", in different cases, feels ugly to me.
You might find this email from Graydon Hoare interesting [7]. Among other things he says:
> If the argument's about consistency, I think the consistent thing to do is to keep ":" as a separator between bindings and types. My eyes get a little fuzzy guessing the associativity of "let x : fn(T) : U;" in a way that they do not when reading "let x : fn(T) -> U;". But I suppose reasonable people could disagre, or phrase the consistency requirement differently. My math professors would cry.
I agree it's weird there's all this syntax talk in a dead thread of a flagged post about a different topic. I guess language (and now community) wars can't help but devolve into this. As for strangeness, all three of these are valid:
let mut a = Vec::<u32>::new();
let mut b = <Vec::<u32>>::new();
let mut c = <Vec<u32>>::new();
but I think most C++ programmers would expect:
let mut d = Vec<u32>::new();
which is not allowed. Pretend it's a different type, because really you'd just use the vec! macro for this.
Last I looked, there's another oddity in the lexer/parser with a.0.1 coming out as Id(a) DOT F64(0.1) and a later stage recovers the intent Id(a) DOT Int(0) DOT Int(1).
> I guess language (and now community) wars can't help but devolve into this.
I suppose I'm a bit more optimistic than you; I feel that most commenters tend to do a pretty good job of not veering into unrelated topics, even when things start getting heated. The lobste.rs discussion on this post, for instance, seems to have avoided these tangents.
> As for strangeness, all three of these are valid:
To mangle a quote from a certain movie, "The strangeness budget is more what you'd call 'guidelines' than actual rules" :P
I agree that most C++ programmers would expect what you showed, but as with many things the turbofish is a tradeoff (unfamiliar syntax vs. ease of parsing). I presume the Rust devs at the time thought the tradeoff was worth it and there just hasn't been sufficient consensus/motivation to change it since then.
I recall someone here talking about the oddity you mention, but I don't remember if they said it was an implementation quirk or something more fundamental to Rust's grammar.
My speculation about the grammar stuff is a little different. I dove into Rust the first time when it was still Graydon's Ocaml+Cyclone style, and the transition to the new syntax felt like it happened overnight. I think there were a lot of talented kids moving really fast, and I think they stumbled in a couple places.
Within reason, syntax doesn't really matter to me though. For instance, I was eager to learn the original sigil-based language. The downside to using familiar syntax in a radically different language is that the differences really catch you by surprise.
I fumbled around for a while before I learned those three bits of turbofish above, and I put them in my notes once I found them. It wasn't an easy thing to search for because the nomenclature and concepts are all different. Generics - not templates, traits - not classes, associated functions - not constructors. I'm still not sure about that last one. More debits to the strangeness budget.
Anyway, syntax and nomenclature are things any serious developer would get past. I thought a lot about starting a blog to document the real stumbling points I hit, but it would be tedious and whiny, and I'm not interested in building an audience.
Sounds like you're far more qualified than I am to be discussing Rust's syntax choices/changes! I haven't been around nearly as long.
> The downside to using familiar syntax in a radically different language is that the differences really catch you by surprise.
Definitely agreed. It's a fine needle to thread for devs and I would not be surprised if there were tradeoffs to be made between familiarity and not importing too much of the inspiring language.
> I thought a lot about starting a blog to document the real stumbling points I hit, but it would be tedious and whiny, and I'm not interested in building an audience.
For what it's worth I personally think I'd find such a thing interesting to read. Sounds like it could make for some good feedback for better onboarding as well (well, assuming you're not talking about stumbling points from your first forays into Rust. But even those can still have some intellectual value, if the interest in Graydon's reflections on Rust's early days are any indication)
Thank you for the friendly compliment, but I'm not qualified to say more than a little about Rust's syntax. I just have speculations about the parts I needed and which surprised me.
As for the blog, it would not have been good for onboarding people to Rust. "Stumbling points" was the wrong phrase. I meant something stronger and more critical than that.
Anyway, thank you for the friendly chat! Time for me to go to bed.
Do you... Do you think this is similar in severity, impact, or even implementation stupidity to *sshhole?
Or is your point just "see? Rust doesn't prevent all stupid errors!".
And yes, *sshhole is a C language problem, even if you technically can achieve it in Rust just like you technically can still write memory vulnerabilities in any memory safe language if you try hard enough. The point is that any more modern language has learned that there are several behaviors where the easy way that's typically taught should be fail-safe. Enforcing error handling is one of these. Zig didn't get on the memory safety train, but it absolutely did on get to error unions. Basically every language that's trying to be a C successor in any way is doing that. Because it turns out the "standard" C error handling and the ways that posix and libraries have traditionally done just sucks and results in idiocy like this.
Even languages like Java at least realized that with try/catch there needs to be some way to enforce error handling downstream (not to say there aren't other languages that don't promote that! You won't find me defending JS on this issue for instance)
Now, can you do errors better in C? Sure you can! It may be a bit more cumbersome, generate a lot more code, and be a bit of a pain in the ass to do, but you absolutely could if you wanted to. Do you know any large popular C codebase that does though? I don't. Returning NULL or errno or -1 is what's commonly done and what most learning materials about C will tell you to do. It's the UNIX convention. Using a sentinel value for errors is just what's done in C and yes, it works just fine most of the time.
But as the post says: this is the issue. C as a language, as a set of standards, as community around them, just treats all of this a skill issue, ignoring that it is possible to increase the safety by making it more convenient to write safe code, because it's not possible to magically get rid of all issues away so why should we bother at all.*
I don't know which of your questions are sincere vs rhetorical. Maybe the one about C codebases is genuine: OpenBSD aims for security through correctness, although it's limited in popularity.
If you actually wanted an answer to any of the others, and maybe if you can calm down a little, I'd be happy to tell you my thoughts.
Yeah, read that, laughed. While the statement may have some truthiness, if one flips it around it is basically saying that the norms of musl community allow programmers to be extra dumb.
Taking a different perspective, there are two intertwined thoughts in the original article and in most comments: 1) language safety, 2) community safety.
Most of the problems described in Rust manuals as things C cannot prevent, such as memory leaks, double frees, and use-after-free, can be detected at compile time in Cake with very few annotations.
> This is not a memory safety vulnerability; rather, it is one of those issues that, people often say, languages with better type systems won’t save you from. This is true! You can very well write a Rust function that returns "" if the password is too long. But the big difference between C and Rust in this regard is that the norms of the Rust community do not allow that.*
I did read it. Again, it is blaming poor engineering on C. It's almost like saying people are e.g. stealing because they belong to certain socio-economic group. Crass.
No, this makes sense. You often see people avoid a good solution in early languages, because it's harder to implement. This impacts security, performance, and overall design.
Consider how much easier it is in rust to do tagged unions that won't have the same pitfalls as C's, how much more likely you are to use a rust map from std rather than custom / imported dependency to do that in C, how much more likely you are to do real Unicode processing in rust vs treating strings as opaque bytes and hoping for the best in C. These are things that happen all the time. We can improve design and engineering a lot by simply providing an easy way to get it implemented.
The first time you need a string map in an existing C code, it's often "an event". Do you spend time rolling your own? Do you include a library? How big - are you pulling the whole glib? Do the strings need fancy locale aware comparison? Do you simplify, take a performance hit, and just scan an array instead?
A lot of the time when there's a logic bug and people comment "that's not a memory safety issue, Rust wouldn't protect from it", it's true, but I also think: "but this issue wouldn't exist because enum/type matching would stop it".
There are two big differences between norms and habits: habits are usually individual while norms apply to a community, and norms have a great deal of social pressure behind them.
If safety features are available and you choose not to use them then that's bad engineering.
I love K&R as much as the next guy but special magic ASCII chars multiply defined by convention over 40 years is exactly the sort of rubbish a good type system can protect against (if used).
"It's a shame, but what can we do? There really isn't anything we can do to prevent memory safety vulnerabilities from happening if the programmer doesn't want to write their code in a robust manner"
Oohhh there’s one more C vulnerability. Oh my gosh, what ever will I do? Should I buy a new smart toaster? I’m not sure. I just really don’t know what to do right now.
It’s called pay attention and f*king upgrade your packages.
This is less a problem with C and more a bad design decision by the musl team. In response to poor practices (programmers not checking what crypt returned), they built an arguably worse one (returning a sentinel value).
Sure, Rust's std::result can make it harder to dumb, but dumb can always find a way.
From the post:
> You can very well write a Rust function that returns "*" if the password is too long. But the big difference between C and Rust in this regard is that the norms of the Rust community do not allow that.
People in glass houses: https://github.com/nextest-rs/nextest/security/advisories/GH...
Whataboutery generally makes it quite clear that your arguments are not in good faith.
There's a difference between whataboutism and calling out hypocrisy, but those are on a spectrum. However, using a sock puppet (or two?) is clearly in bad faith.
Even though it's been beaten to death, I think there are still interesting discussions to be had about the problems in C, possible solutions to those, and which ones Rust provides at what cost. Really, I wish we had a few more options in the "memory safe languages without a garbage collector" space. However, junior evangelists who make themselves feel safer by finger pointing aren't likely to add much value to the conversation.
This particular post is unusual, because it's not even about a flaw in C. Appealing to Rust's culture and norms isn't compelling to me, and it will be insufferable if the newbies start regularly trotting out this nonsense.
I pasted that link to show that Rust doesn't save you from logic errors. I think the best way to get better at programming is to keep doing it while learning from mistakes. I think the best way to proselytize Rust is to finish and deliver good software with it.
> I think the best way to proselytize Rust is to finish and deliver good software with it.
I think you might want to look up my body of work, and also lay off on the baseless accusations of sockpuppetry.
I think people should be proud of their work and humble about its importance. In most circumstances, I don't like to criticize things people create and give away for free, but since you invited, MUSL is more significant and valuable to me.
Edit: The reply by aw1621107 is of ridiculously low quality, and its purpose is presumably to distract and troll. Please consider disregarding and flagging all current and future comments by aw1621107.
Rust syntax is indeed ugly. For instance, why copy C++'s "::" for paths when Rust was a greenfield programming language without the burden of backwards compatibility? And why have some annoying ambiguities, exceptional cases or general annoyances regarding syntax, like in:
- https://doc.rust-lang.org/reference/statements.html ("= Expression except LazyBooleanExpression or end with a }").
- Turbofish, could probably have been avoided by not using "<" and ">" both as type argument delimiters and as an operator for comparison. Scala 2 for example does it differently, using "[" and "]" for type argument delimiting.
- Using two different operators for what one could call "type annotations", namely both ":" and "->", in different cases, feels ugly to me.
As an aside, the contemporary feeling among many programming language designers (not specific to Rust) is to be negative about parenthesis, but parenthesis can have many advantages, and there are often ways to mitigate or cut down on their verbosity.
Leaving ugly Rust syntax aside, I agree that C is significantly limited regarding using some abstractions ergonomically. Pattern matching support, and some ways of ergonomically doing tagged unions, would arguably be nice for C, though C is minimalistic on purpose.
Though, Rust has significant weaknesses and failings even on that point. For instance, out-of-memory handling is as far as I know an active research subject in Rust regarding at least its standard library, regarding how not to just abort the process upon OOM, while C has that handled since forever, despite C requiring extra care, steps and verbosity to handle it. Also one reason why some developers like Zig, Odin, etc. over Rust.
And then there is the mess regarding panics that are not exceptions, except when panics are unwound and they can in limited cases be caught like with catch_unwind, and some Rust programs, including regarding "unsafe", has to worry about "unwind safety", similar to C++ "exception safety". Some Rust programs, in particular servers, often have a design where panics are recovered from. And there is pinning.
And then there is the issue of linking, which is a challenge regarding Rust and many Linux distributions. And where some Rust pushers claim that the approach of those Linux distributions is simply to be considered bad and outdated, instead of trying to figure out ways to see if it could be supported by Rust. And Rust is infamous for sometimes having ridiculously large build artifact sizes:
https://www.reddit.com/r/rust/comments/1ufaef8/my_rust_works...
> My Rust workspace is 75GB. Is this normal for long-term projects?
> yeah totally normal, it's all in target/.
> I ran `cargo clean` a few months ago and it cleared out *300GB* of artefacts etc. So yeah 75GB for everything is fine lmao.
> My record is 1.6TB, cleaned it up a week and a half ago.
And regarding library design, https://amutable.com/blog/systemd-loves-rust describes different approaches between handling of path lookup:
> To reiterate an earlier point by way of example, none of the path lookup functionality provided by Rust is currently acceptable in the security sensitive system components provided by systemd.
And then there is the social stuff, like how some Rust pushers vehemently attacked gccrs for daring to attempt an alternative to rustc.
Kind of curious where all this syntax talk came from given neither the article nor other comments are talking about it. Much of the rest seems to have a tenuous connection at best to the comment you are replying to.
That being said...
> For instance, why copy C++'s "::" for paths when Rust was a greenfield programming language without the burden of backwards compatibility?
The concept of a strangeness budget [0] is probably relevant here - if you want to appeal to a particular audience (C++ programmers in this case), you probably want to limit "strangeness" to places where it's likely to make the largest impact. From this lens it might be interesting to look at your question from the other direction: What would the benefit be of not copying C++'s syntax in this context, if any, and is that worth spending part of your strangeness budget?
Also this comment [1] from a few days ago seems well-timed, though it's answering a slightly different question:
> The `::` syntax allows the compiler to identify and resolve all the static paths before any type inference or other analysis. This is important because macros are expanded between these stages, and can in turn introduce more static names which must also be resolved before any further compilation.
Unfortunately I haven't been able to find an authoritative explanation of why :: instead of something else, though given how old it is [2] I'd guess it's one of those strangeness budget decisions.
> ("= Expression except LazyBooleanExpression or end with a }").
Based on the syntax diagram this bit is for let-else. If you look at the RFC [3] you'll get the answer you seek:
> While allowing e.g. if {} else {} directly in the expression position is technically feasible this RFC proposes it be disallowed for programmer clarity so as to avoid ... else {} else {} situations as discussed in the drawbacks section. Boolean matches are not useful with let-else and so lazy boolean expressions are disallowed for reasons noted in future-possibilities. These types of expressions can still be used when combined in a less ambiguous manner with parentheses, which is allowed under the two expression restrictions.
> Turbofish, could probably have been avoided by not using "<" and ">" both as type argument delimiters and as an operator for comparison. Scala 2 for example does it differently, using "[" and "]" for type argument delimiting.
Rust did in fact use square brackets for generics early on, but that was later changed to angle brackets [4, 5]. I haven't been able to dig up explicit discussions as to why, though again I suspect strangeness budget concerns were at least part of the reason.
Using square brackets for types is probably not a change that can be made in isolation either; at a minimum you'd need to consider how that interacts with indexing/function calls/etc. and how those in turn interact with traits (e.g., if manual implementation of function traits is ever stabilized).
Also, from what I understand the turbofish was never strictly necessary, and in fact it actually is possible for Rust to eliminate the need for the turbofish thanks to the magic of editions ([6]). It's "just" a matter of someone taking up the cause and gathering sufficient consensus.
> Using two different operators for what one could call "type annotations", namely both ":" and "->", in different cases, feels ugly to me.
You might find this email from Graydon Hoare interesting [7]. Among other things he says:
> If the argument's about consistency, I think the consistent thing to do is to keep ":" as a separator between bindings and types. My eyes get a little fuzzy guessing the associativity of "let x : fn(T) : U;" in a way that they do not when reading "let x : fn(T) -> U;". But I suppose reasonable people could disagre, or phrase the consistency requirement differently. My math professors would cry.
[0]: https://steveklabnik.com/writing/the-language-strangeness-bu...
[1]: https://news.ycombinator.com/item?id=50369228
[2]: https://web.archive.org/web/20130118053116/http://static.rus...
[3]: https://rust-lang.github.io/rfcs/3137-let-else.html
[4]: https://github.com/rust-lang/rust/commit/70b009178cf050d5bb7...
[5]: https://github.com/rust-lang/rust/commit/0107d6a81d2180d3a1e...
[6]: https://github.com/rust-lang/rfcs/pull/2544
[7]: https://web.archive.org/web/20201111221825/https://mail.mozi...
I agree it's weird there's all this syntax talk in a dead thread of a flagged post about a different topic. I guess language (and now community) wars can't help but devolve into this. As for strangeness, all three of these are valid:
but I think most C++ programmers would expect:
which is not allowed. Pretend it's a different type, because really you'd just use the vec! macro for this.
Last I looked, there's another oddity in the lexer/parser with a.0.1 coming out as Id(a) DOT F64(0.1) and a later stage recovers the intent Id(a) DOT Int(0) DOT Int(1).
> I guess language (and now community) wars can't help but devolve into this.
I suppose I'm a bit more optimistic than you; I feel that most commenters tend to do a pretty good job of not veering into unrelated topics, even when things start getting heated. The lobste.rs discussion on this post, for instance, seems to have avoided these tangents.
> As for strangeness, all three of these are valid:
To mangle a quote from a certain movie, "The strangeness budget is more what you'd call 'guidelines' than actual rules" :P
I agree that most C++ programmers would expect what you showed, but as with many things the turbofish is a tradeoff (unfamiliar syntax vs. ease of parsing). I presume the Rust devs at the time thought the tradeoff was worth it and there just hasn't been sufficient consensus/motivation to change it since then.
I recall someone here talking about the oddity you mention, but I don't remember if they said it was an implementation quirk or something more fundamental to Rust's grammar.
My speculation about the grammar stuff is a little different. I dove into Rust the first time when it was still Graydon's Ocaml+Cyclone style, and the transition to the new syntax felt like it happened overnight. I think there were a lot of talented kids moving really fast, and I think they stumbled in a couple places.
Within reason, syntax doesn't really matter to me though. For instance, I was eager to learn the original sigil-based language. The downside to using familiar syntax in a radically different language is that the differences really catch you by surprise.
I fumbled around for a while before I learned those three bits of turbofish above, and I put them in my notes once I found them. It wasn't an easy thing to search for because the nomenclature and concepts are all different. Generics - not templates, traits - not classes, associated functions - not constructors. I'm still not sure about that last one. More debits to the strangeness budget.
Anyway, syntax and nomenclature are things any serious developer would get past. I thought a lot about starting a blog to document the real stumbling points I hit, but it would be tedious and whiny, and I'm not interested in building an audience.
Sounds like you're far more qualified than I am to be discussing Rust's syntax choices/changes! I haven't been around nearly as long.
> The downside to using familiar syntax in a radically different language is that the differences really catch you by surprise.
Definitely agreed. It's a fine needle to thread for devs and I would not be surprised if there were tradeoffs to be made between familiarity and not importing too much of the inspiring language.
> I thought a lot about starting a blog to document the real stumbling points I hit, but it would be tedious and whiny, and I'm not interested in building an audience.
For what it's worth I personally think I'd find such a thing interesting to read. Sounds like it could make for some good feedback for better onboarding as well (well, assuming you're not talking about stumbling points from your first forays into Rust. But even those can still have some intellectual value, if the interest in Graydon's reflections on Rust's early days are any indication)
Thank you for the friendly compliment, but I'm not qualified to say more than a little about Rust's syntax. I just have speculations about the parts I needed and which surprised me.
As for the blog, it would not have been good for onboarding people to Rust. "Stumbling points" was the wrong phrase. I meant something stronger and more critical than that.
Anyway, thank you for the friendly chat! Time for me to go to bed.
Do you... Do you think this is similar in severity, impact, or even implementation stupidity to *sshhole?
Or is your point just "see? Rust doesn't prevent all stupid errors!".
And yes, *sshhole is a C language problem, even if you technically can achieve it in Rust just like you technically can still write memory vulnerabilities in any memory safe language if you try hard enough. The point is that any more modern language has learned that there are several behaviors where the easy way that's typically taught should be fail-safe. Enforcing error handling is one of these. Zig didn't get on the memory safety train, but it absolutely did on get to error unions. Basically every language that's trying to be a C successor in any way is doing that. Because it turns out the "standard" C error handling and the ways that posix and libraries have traditionally done just sucks and results in idiocy like this. Even languages like Java at least realized that with try/catch there needs to be some way to enforce error handling downstream (not to say there aren't other languages that don't promote that! You won't find me defending JS on this issue for instance)
Now, can you do errors better in C? Sure you can! It may be a bit more cumbersome, generate a lot more code, and be a bit of a pain in the ass to do, but you absolutely could if you wanted to. Do you know any large popular C codebase that does though? I don't. Returning NULL or errno or -1 is what's commonly done and what most learning materials about C will tell you to do. It's the UNIX convention. Using a sentinel value for errors is just what's done in C and yes, it works just fine most of the time.
But as the post says: this is the issue. C as a language, as a set of standards, as community around them, just treats all of this a skill issue, ignoring that it is possible to increase the safety by making it more convenient to write safe code, because it's not possible to magically get rid of all issues away so why should we bother at all.*
I don't know which of your questions are sincere vs rhetorical. Maybe the one about C codebases is genuine: OpenBSD aims for security through correctness, although it's limited in popularity.
If you actually wanted an answer to any of the others, and maybe if you can calm down a little, I'd be happy to tell you my thoughts.
Yeah, read that, laughed. While the statement may have some truthiness, if one flips it around it is basically saying that the norms of musl community allow programmers to be extra dumb.
Taking a different perspective, there are two intertwined thoughts in the original article and in most comments: 1) language safety, 2) community safety.
Each needs to assessed on its own.
Memory safety is something that can be added to C. For instance, Cake in safety mode requires explicit handling of potentially null return values.
http://cakecc.org/playground.html?code=I2luY2x1ZGUgPHN0ZGlvL...
Most of the problems described in Rust manuals as things C cannot prevent, such as memory leaks, double frees, and use-after-free, can be detected at compile time in Cake with very few annotations.
That's RAII for you and that's the easy part. Rust has so much more safety features C can't even dream of having. Its type system for a start.
> The engineering norms of C developers allow for "*" to be an acceptable sentinel value indicating an error[...]
No. Just No. It's not.
Except *sshole isn't a memory safety vulnerability - it's a logic bug. And logic bugs happen in every programming language.
Yes, this is explicitly discussed in the post:
> This is not a memory safety vulnerability; rather, it is one of those issues that, people often say, languages with better type systems won’t save you from. This is true! You can very well write a Rust function that returns "" if the password is too long. But the big difference between C and Rust in this regard is that the norms of the Rust community do not allow that.*
It is idiomatic C to use in-band sentinel values (NULL is the most prominent example). It is very much not idiomatic rust.
It's also idiomatic in C to use the return value solely for sentinels and pass the actual result in an output variable.
What does it have to do with C? It's just poor design.
It's like blaming poor driving on one's tracksuit.
I think if you read the post you will find the answer to your question.
I did read it. Again, it is blaming poor engineering on C. It's almost like saying people are e.g. stealing because they belong to certain socio-economic group. Crass.
No, this makes sense. You often see people avoid a good solution in early languages, because it's harder to implement. This impacts security, performance, and overall design.
Consider how much easier it is in rust to do tagged unions that won't have the same pitfalls as C's, how much more likely you are to use a rust map from std rather than custom / imported dependency to do that in C, how much more likely you are to do real Unicode processing in rust vs treating strings as opaque bytes and hoping for the best in C. These are things that happen all the time. We can improve design and engineering a lot by simply providing an easy way to get it implemented.
The first time you need a string map in an existing C code, it's often "an event". Do you spend time rolling your own? Do you include a library? How big - are you pulling the whole glib? Do the strings need fancy locale aware comparison? Do you simplify, take a performance hit, and just scan an array instead?
A lot of the time when there's a logic bug and people comment "that's not a memory safety issue, Rust wouldn't protect from it", it's true, but I also think: "but this issue wouldn't exist because enum/type matching would stop it".
It says "the norms of the Rust community" would prevent this vulnerability. So, it's about habits. The terms Rust and C in fact refer to habits.
There are two big differences between norms and habits: habits are usually individual while norms apply to a community, and norms have a great deal of social pressure behind them.
That's true. I just didn't want to keep saying norm because somebody might ask "who's Norm?".
I could see the same thing in Python. Except instead it would be an inhandled none they didn’t like so they would return a *.
If safety features are available and you choose not to use them then that's bad engineering.
I love K&R as much as the next guy but special magic ASCII chars multiply defined by convention over 40 years is exactly the sort of rubbish a good type system can protect against (if used).
obligatory https://xeiaso.net/shitposts/no-way-to-prevent-this/CVE-2024...
"It's a shame, but what can we do? There really isn't anything we can do to prevent memory safety vulnerabilities from happening if the programmer doesn't want to write their code in a robust manner"
Never
The authors obviously don't do small embedded s/w dev.
Additionally, this is part of a trend that expects the library to do a large part of the application development.
I just really hate rust compile times man
Oohhh there’s one more C vulnerability. Oh my gosh, what ever will I do? Should I buy a new smart toaster? I’m not sure. I just really don’t know what to do right now.
It’s called pay attention and f*king upgrade your packages.