Why Is Sqlite Coded in C
Posted3 months agoActive3 months ago
sqlite.orgTechstoryHigh profile
calmmixed
Debate
70/100
SqliteC Programming LanguageRust Programming LanguageSoftware Development
Key topics
Sqlite
C Programming Language
Rust Programming Language
Software Development
The SQLite team explains why they chose C as their programming language, sparking a discussion on the merits of C vs other languages like Rust, and the challenges of rewriting a mature codebase.
Snapshot generated from the HN discussion
Discussion Activity
Very active discussionFirst comment
28m
Peak period
120
0-12h
Avg / period
16
Comment distribution160 data points
Loading chart...
Based on 160 loaded comments
Key moments
- 01Story posted
Oct 14, 2025 at 4:32 PM EDT
3 months ago
Step 01 - 02First comment
Oct 14, 2025 at 5:00 PM EDT
28m after posting
Step 02 - 03Peak activity
120 comments in 0-12h
Hottest window of the conversation
Step 03 - 04Latest activity
Oct 21, 2025 at 9:55 AM EDT
3 months ago
Step 04
Generating AI Summary...
Analyzing up to 500 comments to identify key contributors and discussion patterns
ID: 45584464Type: storyLast synced: 11/20/2025, 8:14:16 PM
Want the full context?
Jump to the original sources
Read the primary article or dive into the live Hacker News thread when you're ready.
Occasionally when working in Lua I'd write something low-level in C++, wrap it in C, and then call the C wrapper from Lua. It's extra boilerplate but damn is it nice to have a REPL for your C++ code.
Edit: Because someone else will say it - Rust binary artifacts _are_ kinda big by default. You can compile libstd from scratch on nightly (it's a couple flags) or you can amortize the cost by packing more functions into the same binary, but it is gonna have more fixed overhead than C or C++.
If I want a "C Library", I want a "C Library" and not some weird abomination that has been surgically grafted to libstdc++ or similar (but be careful of which version as they're not compatible and the name mangling changes and ...).
This isn't theoretical. It's such a pain that the C++ folks started resorting to header-only libraries just to sidestep the nightmare.
This makes me less safe rather than more. Note that there is a substantial double standard here, we could never in the name of safety impose this level of burden from C tooling side because maintainers would rightfully be very upset (even toggling a warning in the default set causes discussions). For the same reason it should be unacceptable to use Rust before this is fixed, but somehow the memory safety absolutists convinced many people that this is more important than everything else. (I also think memory safety is important, but I can't help but thinking that pushing for Rust is more harmful to me than good. )
Zig gives the programmer more control than Rust. I think this is one of the reasons why TigerBeetle is written in Zig.
From section "1.2 Compatibility". How easy is it to embed a library written in Zig in, say, a small embedded system where you may not be using Zig for the rest of the work?
Also, since you're the submitter, why did you change the title? It's just "Why is SQLite Coded in C", you added the "and not Rust" part.
From the site guidelines: https://news.ycombinator.com/newsguidelines.html
More control over what exactly? Allocations? There is nothing Zig can do that Rust can’t.
I read your response 3 times and I truly don't know what you mean. Mind explaining with a simple example?
Hash maps in zig std are another great example, where you can use adapter to completely change how the data is stored and accessed while keeping the same API [1]. For example to have map with limited memory bound that automatically truncates itself, in rust you need to either write completely new data structure for this or rely on someone's crate again (indexmap).
Errors in zig compose also better, in rust I find error handling really annoying. Anyhow makes it better for application development but you shouldn't use it if writing libraries.
When writing zig I always feel like I can reuse pieces of existing code by combining the building blocks at hand (including freestanding targets!). While in rust I always feel like you need go for the fully tailored solution with its own gotchas, which is ironic considering how many crates there are and how many crates projects depend on vs. typical zig projects that often don't depend on lots of stuff.
1: https://zig.news/andrewrk/how-to-use-hash-map-contexts-to-sa...
I mean yeah, allocations. Allocations are always explicit. Which is not true in C++ or Rust.
Personally I don't think it's that big of a deal, but it's a thing and maybe some people care enough.
...If you're using the alloc/std crates (which to be fair, is probably the vast majority of Rust devs). libcore and the Rust language itself do not allocate at all, so if you use appropriate crates and/or build on top of libcore yourself you too can have an explicit-allocation Rust (though perhaps not as ergonomic as Zig makes it).
This is annoying in Rust. To me array accesses aren't the most annoying, it's match{} branches that will never been invoked.
There is unreachable!() for such situations, and you would hope that:
is recognised by the Rust tooling and just ignored. That's effectively the same as SQLite is doing now by not doing the check. But it isn't ignored by the tooling: unreachable!() is reported as a missed line. Then there is the test code coverage including the standard output by default, and you have to use regex's on path names to remove it.Your example does what [] does already, it’s just a more verbose way of writing the same thing. It’s not the same behavior as sqlite.
Array access was just an example. My point was that Rust makes 100% code coverage for unit tests well neigh impossible.
For those of us who like 100% test coverage, that is a major annoyance. For way I use Rust that could be fixed by the tooling simply not counting unreachable!() lines as unreached. For Sqlite, who does branch coverage testing on the compiled binary you would have to go a step further, and provide a compile time option that elides all paths that lead to unreachable!() from the binary. I recall Sqlite saying when they changed to 100% branch coverage, their bug reports dropped by a factor of 7. I hope I remember that correctly. If I do, I'm pretty sure they won't be looking at Rust until they can achieve the same outcome. They can't come close now.
Replacing every array access with get_unchecked() and the consequent explosion of unsafe area's wouldn't fly with anyone I worked with. You did say to me in another thread unsafe is perfectly fine in Rust programs, but you are literally the only person holding that opinion I come across.
https://algora.io/challenges/turso "Turso is rewriting SQLite in Rust ; Find a bug to win $1,000"
------
- Dec 10, 2024 : "Introducing Limbo: A complete rewrite of SQLite in Rust"
https://turso.tech/blog/introducing-limbo-a-complete-rewrite...
- Jan 21, 2025 - "We will rewrite SQLite. And we are going all-in"
https://turso.tech/blog/we-will-rewrite-sqlite-and-we-are-go...
- Project: https://github.com/tursodatabase/turso
Status: "Turso Database is currently under heavy development and is not ready for production use."
turso has 341 rust source files spread across tens of directories and 514 (!) external dependencies that produce (in release mode) 16 libraries and 7 binaries with tursodb at 48M and libturso_sqlite3.so at 36M.
looks roughly an order of magnitude larger to me. it would be interesting to understand the memory usage characteristics in real-world workloads. these numbers also sort of capture the character of the languages. for extreme portability and memory efficiency, probably hard to beat c and autotools though.
I suppose SQLite might use a C linter tool that can prove the bounds checks happen at a higher layer, and then elide redundant ones in lower layers, but... C compilers won't do that by default, they'll just write memory-unsafe machine code. Right?
https://news.ycombinator.com/item?id=28278859 - August 2021
https://news.ycombinator.com/item?id=16585120 - March 2018
The current doc no longer has any paragraphs about security, or even the word security once.
The 2021 edition of the doc contained this text which no longer appears: 'Safe languages are often touted for helping to prevent security vulnerabilities. True enough, but SQLite is not a particularly security-sensitive library. If an application is running untrusted and unverified SQL, then it already has much bigger security issues (SQL injection) that no "safe" language will fix.
It is true that applications sometimes import complete binary SQLite database files from untrusted sources, and such imports could present a possible attack vector. However, those code paths in SQLite are limited and are extremely well tested. And pre-validation routines are available to applications that want to read untrusted databases that can help detect possible attacks prior to use.'
https://web.archive.org/web/20210825025834/https%3A//www.sql...
Huh it's not everyday that I hear a genuinely new argument. Thanks for sharing.
Someone please correct me if I'm misunderstanding the argument.
Rust does not stop you from writing code that accesses out of bounds, at all. It just makes sure that there's an if that checks.
By the same logic one could also claim that tail recursion optimisation, or loop unrolling are also dangerous because they change the way code works, and your tests don't cover the final output.
I don’t think anyone would find the idea compelling that “you are only responsible for the code you write, not the code that actually runs” if the code that actually runs causes unexpected invalid behavior on millions of mobile devices.
Google already ships binaries compiled with Rust in Android. They are actually system services which are more critical than SQLite storage of apps.
Moreover Rust version of SQLite can ship binaries compiled with a qualified compiler like Ferrocene: https://ferrocene.dev/en/ (which is the downstream, qualified version of standard Rust compiler rustc). In qualification process the compiler is actually checked whether it generates reasonable machine code against a strict set of functional requirements.
Most people don't compile SQLite with qualified versions of GCC either. So this exact argument actually can be turned against them.
That's a bizarre claim. The source code isn't the product, and the product is what has to work. If a compiler or OS bug causes your product to function incorrectly, it's still your problem. The solution is to either work around the bug or get the bug fixed, not just say "I didn't write the bug, so deal with it."
There is a difference between "gcc 4.8 is buggy, let's not use it" and "let's write unit tests for gcc". If you are suspicious about gcc, you should submit your patches to gcc, not vendor them in your own repo.
Are you asking whether I write integration tests? Yes, I do. And at work there's a whole lot of acceptance testing too.
>There is a difference between "gcc 4.8 is buggy, let's not use it" and "let's write unit tests for gcc".
They're not proposing writing unit tests for gcc, only actually testing what gcc produces from their source. You know, by executing it like tests tend to do. Testing only the first party source would mean relying entirely on static source code analysis instead.
Exactly. You don't need unit tests for the binary output. You want to test whether the executable behaves as expected. Therefore "rust adds extra conditional branches that are never entered, and we can't test those branches" argument is not valid.
If sqlite were to read one byte over the end of an array, it's unlikely to lose your data. Rust would guarantee that to lose data.
Hipp worked as a military contractor for battleships, furthermore years later SQLite was under contract under every proto-smartphone company in the USA. Under these constraints you maybe are not responsible to test what the compiler spits out across platforms and different compilers, but doing that makes the project a lot more reliable, makes it sexier for embedded and weapons.
Some languages I was aware of are defined so that if what you wrote could be a tail call it is. However you might write code you thought was a tail call and you were wrong - in such languages it only blows up when it recurses too deep and runs out of stack. AIUI the Rust feature would reject this code.
This is not correct for every industry.
If the check never fails, it is logically equivalent to not having the check. If the code isn't "correct" and the panic is reached, then the equivalent c code would have undefined behavior, which can be much worse than a panic.
Your second case implies that it is reachable.
If you have the second case, I would much rather have a panic than undefined behavior. As mentioned in another comment, in c indexing an array is semantically equivalent to:
In fact a c compiler could put in a check and abort if it is out of bounds, like rust does and still be in spec. But the undefined behavior could also cause memory corruption, or cause some other subtle bug.The way I was thinking about it was: if you somehow magically knew that nothing added by the compiler could ever cause a problem, it would be redundant to test those branches. Then wondering why a really well tested compiler wouldn't be equivalent to that. It sounds like the answer is, for the level of soundness sqlite is aspiring to, you can't make those assumptions.
This is a dubious statement. In Rust, the array indexing operator arr[i] is syntactic sugar for calling the function arr.index(i), and the implementation of this function on the standard library's array types is documented to perform a bounds-check assertion and access the element.
So the checks aren't really implicitly added -- you explicitly called a function that performs a bounds check. If you want different behavior, you can call a different, slightly-less-ergonomic indexing function, such as `get` (which returns an Option, making your code responsible for handling the failure case) or `get_unchecked` (which requires an unsafe block and exhibits UB if the index is out of bounds, like C).
Automatic array bounds checks can get hit by corrupted data. Thereby leading to a crash of exactly the kind that SQLite tries to avoid. With complete branch testing, they can guarantee that the test suite includes every kind of corruption that might hit an array bounds check, and guarantee that none of them panic. But if the compiler is inserting branches that are supposed to be inaccessible, you can't do complete branch testing. So now how do you know that you have tested every code branch that might be reached from corrupted data?
Furthermore those unused branches are there as footguns which are reachable with a cosmic ray bit flip, or a dodgy CPU. Which again undermines the principle of keeping running if at all possible.
The use case that SQLite has chosen to optimize for is critical embedded software. As described in https://www.sqlite.org/qmplan.html, the standard that they base their efforts on is a certification for use in aircraft. If mission critical software on a plane is allowed to crash, this can render the controls inoperable. Which is likely to lead to a very literal crash some time later.
The result is software that has been optimized to do the right thing if at all possible, and to degrade gracefully if that is not possible.
Note that the open source version of SQLite is not certified for use in aviation. But there are versions out there that have been certified. (The difference is a ton of extra documentation.) And in fact SQLite is in use by Airbus. Though the details of what exactly for are not, as far as I know, public.
If this documented behavior is not what you want for your use case, then you should consider using another database. Though, honestly, no other database comes remotely close when it comes to software quality. And therefore I doubt that "degrade as documented rather than crash" is a good reason to avoid SQLite. (There are lots of other potential reasons for choosing another database.)
I fully recognize that political definitions drive purchases, so it's meaningful to a project either way. but that doesn't make it a valid technical argument.
If it can avoid crashing, other functionality may continue to work fine.
But the choice is not just political. There are very meaningful technical differences for code that potentially winds up embedded in other software, and could be inside of literal embedded software.
The first is memory. It takes memory to run whatever is responsible for detecting the crash, relaunching, and starting up a supervisor. This memory is not free. Which is one of the reasons why Erlang requires at a minimum 10 MB or so of memory. By contrast the overhead of SQLite is something like half a MB. This difference is very significant for people putting software into medical devices, automotive controllers, and so on. All of which are places where SQLite is found, but Erlang isn't.
The second is concurrency. Erlang's concurrency model leaks - you can't embed it in software without having to find a way to fit Erlang concurrency in. This isn't a problem if Erlang already is in your software stack. But that's an architectural constraint that would be a problem in many of the contexts that SQLite is actually used in.
Remember, SQLite is not optimized for your use case. It is optimized for embedded software that needs to try to keep running when things go wrong. It just happens to be so good that it is useful for you.
This state can be "the database is corrupt", allowing for a restore from backup, it can also be "shut down gracefully" and communicate that.
Especially for databases there is a ton to concinder: what about database entries that are not committed yet? When you run into oom, should you at least try to commit the data still in ram by freeing as much space as possible?
Also you rarely need to actually access by index - you could just access using functional methods on .iter() which avoids the bounds check problem in the first place.
I'm checking to see how array access is implemented, whether through deref to slice, or otherwise.
sure safety checks are added but
it's ignoring that many of such checks get reliably optimized away
worse it's a bit like saying "in case of a broken invariant I prefer arbitrary potential highly problematic behavior over clean aborts (or errors) because my test tooling is inadequate"
instead of saying "we haven't found adequate test tooling" for our use case
Why inadequate? Because technically test setups can use
1. fault injection to test such branches even if normally you would never hit them
2. for many of such tests (especially array bound checks) you can pretty reliably identify them and then remove them from your test coverage statistic
idk. what the tooling of rust wrt this is in 2025, but around the rust 1.0 times you mainly had C tooling you applied to rust so you had problems like that back then.
I wouldn't put it that way. Usually when we say the compiler is "incorrect", we mean that it's generating code that breaks the observable behavior of some program. In that sense, adding extra checks that can't actually fail isn't a correctness issue; it's just an efficiency issue. I'd usually say the compiler is being "conservative" or "defensive". However, the "100% branch testing" strategy that we're talking about makes this more complicated, because this branch-that's-never-taken actually is observable, not to the program itself but to its test suite.
Certainly don't get me wrong, SQLite is one of the best and most thoroughly tested libraries out there. But this was an argument to have 4 arguments. That's because 2 of the arguments break down as "Those languages didn't exist when we first wrote SQLite and we aren't going to rewrite the whole library just because a new language came around."
Any language, including C, will emit or not emit instructions that are "invisible" to the author. For example, whenever the C compiler decides it can autovectorize a section of a function it'll be introducing a complicated set of SIMD instructions and new invisible branch tests. That can also happen if the C compiler decides to unroll a loop for whatever reason.
The entire point of compilers and their optimizations is to emit instructions which keep the semantic intent of higher level code. That includes excluding branches, adding new branches, or creating complex lookup tables if the compiler believes it'll make things faster.
Dr Hipp is completely correct in rejecting Rust for SQLite. Sqlite is already written and extremely well tested. Switching over to a new language now would almost certainly introduce new bugs that don't currently exist as it'd inevitably need to be changed to remain "safe".
It is.
> then switching to rust would be trivial
So prove it. Hint: it's not trivial.
no, you still need to rewrite, re-optimize, etc. everything
it would make it much easier to be fully compatible, sure, but that doesn't make it trivial
furthermore part of it's (mostly internal) design are strongly influenced by C specific dev-UX aspects, so you wouldn't write them the same, so test for them (instead of integration tests) may not apply
which in general also means that you most likely would break some special purpose/usual user which do have "brittle" (not guaranteed) assumptions about SQLite
if you have code which very little if at all changes and has no major issues, don't rewrite it
but most of the new "external" things written around SQLite, alternative VFS impl. etc. tend to be at most partially written in C
Presumably this is why they do 100% test coverage. All of those instructions would be tested and not invisible to the test suite
A new compiler, new flags, a new version. These all can create new invisible untested branches.
Dr. Hipp's position is paraphrased as, “I cannot trust the compilers, so I test the binaries; the source code may have UBs or run into compiler bugs, but I know the binaries I distribute are correct because they were thoroughly tested" at https://blog.regehr.org/archives/1292. There, Dr. John Regehr, a researcher in undefined behavior, found some undefined behavior in the SQLite source code, which kicked off a discussion of the implications of UB given 100% MC/DC coverage of the binaries of every supported platform.
(I suppose the argument at this point is, "Users may use a new compiler, flag, or version that creates untested code, but that's not nearly as bad as _all_ releases and platforms containing untested code.")
0: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.get...
This feels like chasing arbitrary 100% test coverage at the expense of safety. The code quality isn’t actually improved by omitting the checks even though it makes testing coverage go up.
"Airbus confirms that SQLite is being used in the flight software for the A350 XWB family of aircraft."
https://www.sqlite.org/famous.html
Next they’ll have to tell me about how they had to turn off inlining because it creates copies of code which adds some dead branches. Bounds checks are just normal inlined code. Any bounds checked language worth its salt has that coverage for all that stuff already.
I don't think I would (personally) ever be comfortable asserting that a code branch in the machine instructions emitted by a compiler can't ever be taken, no matter what, with 100% confidence, during a large fraction of situations in realistic application or library development, as to do so would require a type system powerful enough to express such an invariant, and in that case, surely the compiler would not emit the branch code in the first place.
One exception might be the presence of some external formal verification scheme which certifies that the branch code can't ever be executed, which is presumably what the article authors are gesturing towards in item D on their list of preconditions.
The choices therefore are:
1. No bound check
2. Bounds check inserted, but that branch isn't covered by tests
3. Bounds check inserted, and that branch is covered by tests
I'm skeptical of the claim that if (3) is infeasible then the next best option is (1)
Because if it is indeed an impossible scenario, then the lack of coverage shouldn't matter. If it's not an impossible scenario then you have an untested case with option (1) - you've overrun the bounds of an array, which may not be a branch in the code but is definitely a different behaviour than the one you tested.
At the point where a load-bearing piece of your quality assurance strategy is 100% branch coverage of the generated machine code, it very much does matter.
> I'm skeptical of the claim that if (3) is infeasible then the next best option is (1)
In the general case, obviously not. But, in the specific case we’re discussing, which is that (2) has the rider of “the development team will be forced to abandon a heretofore important facet of their testing strategy at the exact moment they are rewriting the entire codebase in a language they are guaranteed to have less expertise in,” I think (1) seems pretty defensible.
If you then can come up a scenario where you need it. Well in fully tested code you do need to test it.
“What gets us into trouble is not what we don't know. It's what we know for sure that just ain't so.” — Mark Twain, https://www.goodreads.com/quotes/738123
A simple array access in C:
...can be thought of as being equivalent to: where the "UB" function can do literally anything. From the perspective of exhaustively testing and formally verifying software, I'd rather have the safe-language equivalent: ...because at least I can reason about what happens if the supposedly-unreachable condition occurs.Dr. Hipp mentions that "Recoding SQLite in Go is unlikely since Go hates assert()", implying that SQLite makes use of assert statements to guard against unreachable conditions. Surely his testing infrastructure must have some way of exempting unreachable assert branches -- so why can't bounds checks (that do nothing but assert undefined behavior does not occur) be treated in the same way?
A more complex C program can have index range checking at a different place than the simple array access. The compiler's flow analysis isn't always able to confirm that the index is guaranteed to be checked. If it therefore adds a cautionary (and unneeded) range check, then this code branch can never be exercised, making the code no longer 100% branch tested.
you basically say if deeply unexpected things happen you prefer you program doing widely arbitrary and as such potentially dangerous things over it having a clean abort or proper error. ... that doesn't seem right
worse it's due to a lack of the used tooling and not a fundamental problem, not only can you test this branches (using fault injection) you also often (not always) can separate them from relevant branches when collecting the branch statistics
so the while argument misses the point (which is tooling is lacking, not extra checks for array bounds and similar)
lastly array bounds checking is probably the worst example they could have given as it
- often can be disabled/omitted in optimized builds
- is quite often optimized away
- has often quite low perf. overhead
- bound check branches are often very easy to identify, i.e. excluding them from a 100% branch testing statistic is viable
- out of bounds read/write are some of the most common cases of memory unsafety leading to security vulnerability (including full RCE cases)
SQLite isn't a program, it's a library used by many other programs. As such, aborting is not an option. It doesn't do "wildly arbitrary" things - it reports errors to the client application and takes it on faith that they will respond appropriately.
There already is an implicit "branch" on every array access in C, it's called an access violation.
Do they test for a segfault on every single array access in the code base? No? Then they don't really have 100% branch coverage, do they?
I think a lot of projects that claim to have 100% coverage are overselling their testing, but SQLite is in another category of thoroughness entirely.
It's like seat belts.
E.g. what if we drive four blocks and then the case occurs when the seatbelt is needed need the seatbelt? Okay, we have an explicit test for that.
But we cannot test everything. We have not tested what happens if we drive four blocks, and then take a right turn, and hit something half a block later.
Screw it, just remove the seatbelts and not have this insane untested space whereby we are never sure whether the seat belt will work properly and prevent injury!
- Rust needs to mature a little more, stop changing so fast, and move further toward being old and boring.
- Rust needs to demonstrate that it can be used to create general-purpose libraries that are callable from all other programming languages.
- Rust needs to demonstrate that it can produce object code that works on obscure embedded devices, including devices that lack an operating system.
- Rust needs to pick up the necessary tooling that enables one to do 100% branch coverage testing of the compiled binaries.
- Rust needs a mechanism to recover gracefully from OOM errors.
- Rust needs to demonstrate that it can do the kinds of work that C does in SQLite without a significant speed penalty.
2. This has been demonstrated.
3. This one hinges on your definition of “obscure,” but the “without an operating system” bit is unambiguously demonstrated.
4. I am not an expert here, but given that you’re testing binaries, I’m not sure what is Rust specific. I know the Ferrocene folks have done some of this work, but I don’t know the current state of things.
5. Rust as a language does no allocation. This OOM behavior is the standard library, of which you’re not using in these embedded cases anyway. There, you’re free to do whatever you’d like, as it’s all just library code.
6. This also hinges on a lot of definitions, so it could be argued either way.
Of course, two libraries that choose different no_std collection types can't communicate...but hey, we're comparing to C here.
like there are some things you can well in C
and this things you can do in rust too, through with a bit of pain and limitations to how you write rust
and then there is the rest which looks "hard but doable" in C, but the more you learn about it the more it's a "uh wtf. nightmare" case where "let's kill+restart and have robustness even in presence of the process/error kernel dying" is nearly always the right answer.
Rust insists on its own package manager "rustup" and frowns on distro maintainers. When Rust is happy to just be packaged by the distro and rustup has gone away, then it will have matured to at least adolescence.
There are other worlds out there than Linux.
the rust version packaged in distros is for compiling rust code shipped as part of the distro. This means it
- is normally not the newest version (which , to be clear, is not bad per see, but not necessary what you need)
- might not have all optional components (e.g. no clippy)
but if you idk. write a server deployed by you company
- you likely want all components
- you don't need to care what version the distro pinned
- you have little reason not to use the latest rust compiler
for other use cases you have other reasons, some need nightly rust, some want to test against beta releases, some want to be able to test against different rust versions etc. etc.
rustup exist (today) for the same reason why a lot of dev projects use project specific copies of all kinds of tooling and libraries which do not match whatever their distro ships: The distro use-case and generic dev-use case have diverging requirements! (Other examples nvm(node), flutter, java etc.).
Also some distros are notorious for shipping outdated software (debian "stable").
And not everything is Linux, rustup works on OSX.
Basically, people use it because they prefer it.
Rather, what makes it hard is the culture and surrounding ecosystem of pinned versions or the latest of everything. That's probably in part the fault of Rustup being recommended, I agree. But it's not nefarious.
I’d love to see rust be so stable that MSRV is an anachronism. I want it to be unthinkable you wouldn’t have any reason not to support Rust from forever ago because the feature set is so stable.
What other languages satisfy this criteria?
- C23: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3096.pdf
- Cobol 2023: https://www.incits.org/news-events/news-coverage/available-n... (random press release since a PDF of the standard didn't immediately show up in a search)
- Fortran 2023: https://wg5-fortran.org/N2201-N2250/N2212.pdf
C2Y has a fair number of already-accepted features as well and it's relatively early in the standard release cycle: https://thephd.dev/c2y-hitting-the-ground-running
C23 seems to have decent support from a few compilers, with GCC leading the pack: https://en.cppreference.com/w/c/compiler_support/23.html
gcobol supports (or at least aims to support?) COBOL 2023: https://gcc.gnu.org/onlinedocs/gcc-15.1.0/gcobol/gcobol.html. Presumably there are other compilers working on support as well.
Intel's Fortran compiler and LFortran have partial support for Fortran 2023 (https://www.intel.com/content/www/us/en/developer/articles/t..., https://docs.lfortran.org/en/usage/). I'd guess that support both from these compilers and from other compilers (Flang?) would improve over time as well..
For example, a very popular library in the systems/embedded space, cjson, works with C89. FreeRTOS works with C99. This is a very common pattern in major libraries. It is very rare that taking a dependency could force you to update your toolchain in the embedded space.
Toolchain updates invalidate your entire testing history and make you revalidate everything, which is often a giant PITA when your testing involves physical things (ex you may have to plug and unplug things, put the system in chambers that replicate certain environmental conditions, etc).
> cJSON is written in ANSI C (C89) in order to support as many platforms and compilers as possible.
And if you look through the issues/PRs you can see there was at least nominal interest in moving to later standards, though it seems those plans never came to fruition.
Not entirely sure about FreeRTOS, but something similar wouldn't surprise me all that much.
It would be amazing if Rust was as the point where people were ready to have conversations like that.
New versions of Rust add so many handy, useful features the cost of staying on old Rust versions is very high right now. Or put differently, the ROI of picking up new versions of Rust is very very VERY good. So most packages do.
Going back to the bigger picture issue though: it’s very common in embedded to lock your toolchain and very rarely change it. This is very desirable to drive down risk and testing burden. It would be sweet if this was more easy to do in Rust. If I need to take an update to a dependency to pick up a bug fix, as it stands today, there’s a good chance these days that I’m going to hit something in the chain that bumped MSRV and will force me to update.
In real-world embedded projects using Rust, you end up with painful options: - chase MSRV of deps and keep on new Rust. Adds risk and testing burden. - minimize deps and reimplement everything yourself. Adds lots of cost. - use C and incur all of the memory safety risks, and assorted foot guns
In the end, Rust still feels worth it, but it’d be super nice to not have to make that trade.
I sort of lean towards thinking that they aren't exactly the same thing in general, but in the embedded C world they're correlated enough that it can be hard to separate individual influences.
> They are saying the changes in the C standard are minor enough that forcing people to upgrade isn’t worth the costs, such as losing compatibility with old toolchains for niche platforms. So they stay on an old C version.
Right, and I think that's a not unreasonable position when worded that way! I'm mostly complaining about the hyperbole - "unthinkable" and "any reason" are pretty high standards which I feel are basically impossible to meet in general - as with many things, there are always tradeoffs and the balance will shift over time.
I do wonder what effect LLVM has had, if any, on the Rust ecosystem's general fast pace. Less support for embedded systems means less early ecosystem pressure that might have motivated more stability, or something along those lines.
The current version of the Rust compiler definitely doesn't -- there's known issues like https://github.com/rust-lang/rust/issues/57893 -- but maybe there's some historical version from before the features that caused those problems were introduced.
Like, in the C world, there's a difference between "the C specification has problems" and "GCC incorrectly implements the C specification". You can make statements about what "the C language" does or doesn't guarantee independently of any specific implementation.
But "the Rust language" is not a specification. It's just a vague ideal of things the Rust team is hoping their compiler will be able to achieve. And so "the Rust language" gets marketed as e.g. having a type system that guarantees memory safety, when in fact no such type system has been designed -- the best we have is a compiler with a bunch of soundness holes. And even if there's some fundamental issue with how traits work that hasn't been resolved for six years, that can get brushed off as merely a compiler bug.
This propagates down into things like Rust's claims about backwards compatibility. Rust is only backwards-compatible if your programs are written in the vague-ideal "Rust language". The Rust compiler, the thing that actually exists in the real world, has made a lot of backwards-incompatible changes. But these are by definition just bugfixes, because there is no such thing as a design issue in "the Rust language", and so "the Rust language" can maintain its unbroken record of backwards-compatibility.
Is it getting brushed off as merely a compiler bug? At least if I'm thinking of the same bug as you [0] the discussion there seems to be more along the lines of the devs treating it as a "proper" language issue, not a compiler bug. At least as far as I can tell there hasn't been a resolution to the design issue, let alone any work towards implementing a fix in the compiler.
The soundness issue that I see more frequently get "brushed off as merely a compiler bug" is the lifetime variance one underpinning cve-rs [1], which IIRC the devs have long decided what the proper behavior should be but actually implementing said behavior is blocked behind some major compiler reworks.
> has made a lot of backwards-incompatible changes
Not sure I've seen much evidence for "a lot" of compatibility breaks outside of the edition system. Perhaps I'm just particularly (un)lucky?
> because there is no such thing as a design issue in "the Rust language"
I'm not sure any of the Rust devs would agree? Have any of them made a claim along those lines?
[0]: https://github.com/rust-lang/rust/issues/57893
[1]: https://github.com/Speykious/cve-rs
Yes, this thread contains an example: https://news.ycombinator.com/item?id=45587209 . (I linked the same bug you did in the comment that that's a reply to.)
The Rust team may see this as a language design issue internally, and I'd be inclined to agree. Rust's outward-facing marketing does not reflect this view.
Ah, my apologies. Not sure exactly how I managed to miss that.
That being said, I guess I might have read that bit of your comment different than you had in mind; I was thinking of whether the Rust devs were dismissing language design issues as compiler bugs, not what third parties (albeit one with an unusually relevant history in this case) may think.
> Rust's outward-facing marketing does not reflect this view.
As above, perhaps I interpret the phrase "outward-facing marketing" differently than you do. I typically associate that (and "marketing" in general, in this context) with more official channels, whether that's official posts or posts by active devs in an official capacity.
I don't put too many things in Cargo.toml and it still pulls like a hundred things
P. S.
I you don't account all the unsafe sections scattered everywhere in all those dependencies.
In C I've seen more half-baked json implementations than I can count on my fingers because using dependencies is too cumbersome in that ecosystem and people just write it themselves but most of the time with more bugs.
ironically if we look at how things play out in practice rust is far more suited as general purpose languages then C, to a point where I would argue C is only a general purpose language on technicality not on practical IRL basis
this is especially ridiculous when they argue C is the fasted general purpose language when that has proven to simply not hold up to larger IRL projects (i.e. not micro benchmarks)
C has terrible UX for generic code re-use and memory management, this often means that in IRL projects people don't write the fasted code. Wrt. memory management it's not rare to see unnecessary clones, as not doing so it to easy to lead to bugs. Wrt. data structures you write the code which is maintainable, robust and fast enough and sometimes add the 10th maximal simple reimplementation (or C macro or similar) of some data structure instead of using reusing some data structures people spend years of fine tuning.
When people switched a lot from C to C++ most general purpose projects got faster, not slower. And even for the C++ to Rust case it's not rare that companies end up with faster projects after the switch.
Both C++ and Rust also allow more optimization in general.
So C is only fastest in micro benchmarks after excluding stuff like fortran for not being general purpose while itself not really being used much anymore for general purpose projects...
231 more comments available on Hacker News