Readit News logoReadit News
ninkendo · 8 days ago
Talking about foundational software but no mention of the biggest missing part of rust IMO: an ABI.

If you want to write an OS in rust and provide rich services for applications to use, you need to offer libraries they can call without needing to recompile when the OS is upgraded. Windows mostly does this with COM, Apple historically had ObjC’s dynamic dispatch (and now Swift’s ABI), Android does this with a JVM and bytecode… Rust can only really offer extern "C", and that really limits how useful dynamic libraries can be.

Doing an ABI without a VM-like layer (JVM, .NET) is really difficult though, and requires you to commit to certain implementation details without ever changing them, so I can understand why it’s not a priority. To my knowledge the only success stories are Swift (which faced the problem head-on) and COM (which has a language-independent IDL and other pain points.) ObjC has such extreme late binding that it almost feels like cheating.

If rust had a full-featured ABI it would be nearly the perfect language. (It would improve compile times as well, because we’d move to a world where dependencies could just be binaries, which would cut compile times massively.)

ameliaquining · 7 days ago
I don't think Rust ever wants to do ABI stability that isn't opt-in, because Rust is about zero-cost abstractions and a stable ABI is not zero-cost. See this explanation of how C++'s de facto stable ABI significantly reduces performance: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p20... [PDF]

For the use case of letting Rust programs dlopen plugins that are also written in Rust, the current solutions are stabby and abi_stable. They're not perfect but they seem to work fairly well in practice.

For more general use cases like cross-language interop, the hope is to get somewhere on crABI: https://github.com/joshtriplett/rfcs/blob/crabi-v1/text/3470... This is intended to be a superior successor to C's ABI, useful for all the same use cases (at least if the code you want to talk to is new enough to support it). Note that this isn't something the Rust maintainers can do unilaterally; there's a need to get buy-in from maintainers of other languages, Linux distros, etc.

I'm not sure that the "everything is a shared library" approach is workable in a language where heap allocation is generally explicit and opt-in. While Swift has some support for stack allocation, most types are heap-allocated, and I think that reduces the extent to which the rest of the language has to be warped to accommodate the stable ABI.

ninkendo · 7 days ago
> While Swift has some support for stack allocation, most types are heap-allocated, and I think that reduces the extent to which the rest of the language has to be warped to accommodate the stable ABI.

Stack vs heap in swift is simple: structs are stack, classes are heap.

This is what makes Swift’s effort so impressive: it works with stack data just as well as heap. Stack allocated types being used across library boundaries has a ton of plumbing to make it work, there’s the concept of a value witness table (like a vtable but for size/stride information) that can allow looking up sizes at runtime. Stack allocation makes heavy use of alloca(), which most languages shy away from (since it means stack frames aren’t of a static size.) It was difficult but swift managed to do it.

For rust to do something similar it would have to fix a lot of cases where object safety is broken, and yeah, this may require changing the language, particularly to support dynamic/runtime size information. (It would also make the dyn keyword effectively obsolete, because to make generics work across library boundaries, any type has to be able to “become” dynamic if the linker needs it to be.)

It would definitely require changes to the language, but Swift provides some inspiring prior art here.

This article goes into the details: https://faultlore.com/blah/swift-abi/

yoshuaw · 7 days ago
We are more or less trying to solve this exact problem with Wasm Components [1]. If core WebAssembly is a portable instruction format, WebAssembly Components are a portable executable/linkable format.

Using them is unfortunately not yet quite as easy as using repr(wasm)/extern "wasm". But with wit-bindgen [2] and the wasm32-wasip2 target [3], it's not that hard either.

[1]: https://youtu.be/tAACYA1Mwv4

[2]: https://github.com/bytecodealliance/wit-bindgen

[3]: https://doc.rust-lang.org/nightly/rustc/platform-support/was...

drogus · 5 days ago
I love WASM and WASI, but it's not nearly the same, unfortunately. Performance takes a hit, you can't use async in a straightforward way, launching hundreds of thousands of tasks is problematic etc. WASM is great for allowing to extend your app, but I don't see it as a replacement for an ABI anytime soon
jenadine · 7 days ago
Not sure if this is really needed though. I mean, it would be more convenient to be able to pass more "things" around interfaces (slices, trait objects, ...), but you can still do everything you need to with an explicit extern "C" abi.

(And I've seen proposal to extend extern ABI to more things)

boredatoms · 7 days ago
Something slightly more than extern C would be nice, such as definitive way to use Option/Result/Enum across the C boundary
robertknight · 7 days ago
I've seen talks on this topic at Rust conferences which seemed strongly influenced by Swift's approach, so that will probably be the direction this ends up going in.
IshKebab · 7 days ago
The WASI Component Model also has a richer ABI. I wonder if that could be copied to native platforms somehow.
loglog · 7 days ago
Not your main point, but how would monomorphization of generics work with binary dependencies?
ninkendo · 7 days ago
It wouldn’t, there would have to be dynamic dispatch. Swift provides some prior art for “monomorphization in the same binary, dynamic dispatch across linker boundaries” as a general approach, and it works pretty well.
anon-3988 · 7 days ago
We already have? Its called C.
nh2 · 7 days ago
I don't really understand the point for writing an "OS in rust and provide rich services for applications to use".

You can use any serialisation method you want (C structs, Protobuf, JSON, ...).

An ABI is for passing data in the programming language and trusting it blindly, e.g. what functions use when calling each other.

Any sane OS still has to validate the data that crosses its boundaries. For example, if you make a Linux system call, it doesn't blindly use a pointer you pass it, but instead actually checks if the pointer belongs to the calling process's address space.

It seems pointless to pick Rust's internal function-calling data serialisation for the OS boundary; that wouldn't make it easier for any userspace programs running in that OS than explicitly defining a serialisation format.

Why would an OS want to limit itself to Rust's internal representation, or why would Rust want to freeze its internal representation, making improvements impossible on both sides? This seems to bring only drawbacks.

The only benefit I can see so far for a stable ABI is dynamic linking (and its use in "plugins" for programs where the code is equally trusted, e.g. writing some dlopen()able `.so` files in Rust, as plugins for Rust-only programs), where you can really "blindly call the symbol". But even there it's a bit questionable, as any mistake or mismatch in ABI can result in immediately memory corruption, which is something that Rust users rather dislike.

IshKebab · 7 days ago
I really like Rust but there are some quite frustrating core paper cuts that I wish would get more attention:

1. Self-referencing structs. Especially where you want to have something like a source file and the parsed AST in the same struct. You can't easily do that at the moment. It would be nice if there was something like an offset reference that made it work. Or something else...

2. The orphan rule. I get it, but it's still annoying. We can do better than newtype wrappers (which sometimes have to be nested 2 or 3 levels deep!).

3. The fact that for reasonable compile time you need to split projects into lots of small crates. Again, I understand the reasons, but the result sucks and we can definitely do better. As I understand it this is because crates are compiled as one compilation unit, and they have to be because circular dependencies are allowed. While that is true, I expect most code doesn't actually have circular dependencies so why not make those opt-in? Or even automatically split items/files within a crate into separate compilation units based on the dependency graph?

There's probably more; this is just what I can remember off the top of my head.

Hopefully that's constructive criticism. Rust is still my favourite programming language by far.

kibwen · 7 days ago
> As I understand it this is because crates are compiled as one compilation unit, and they have to be because circular dependencies are allowed

Rust allows circular dependencies between modules within a single compilation unit, but crates can't be circular dependencies.

> I expect most code doesn't actually have circular dependencies

Not true, most code does have benign circular dependencies, though it's not common to realize this. For example, consider any module that contains a `test` submodule, that's a circular dependency, because the `test` submodule imports items from the parent (it has to, because it wants to test them), but also the parent can refer to the submodule, because that's how modules and submodules fundamentally work. To eliminate the circular dependency you would need to have all test functions within every compilation unit defined within the root of the crate (not in a submodule off of the root; literally defined in the root namespace itself).

IshKebab · 7 days ago
> Rust allows circular dependencies between modules within a single compilation unit, but crates can't be circular dependencies.

Yes, that's what I was saying.

> that's a circular dependency, because the `test` submodule imports items from the parent

This isn't a circular dependency because the parent doesn't import anything from the test module.

It would mean your compilation unit might have to be a subset of a module, but I think that's fine?

nicoburns · 7 days ago
Totally agree with 1 and 2.

Would add 4: partial self borrows (ability for methods to only part of their struct).

For 3, I think the low hanging fruit is probably better support for just using multiple crates (support for publishing them as one package for example).

Ygg2 · 7 days ago
Weakening the orphan rule would probably wreck too many crates. It's one of those changes that sounds great on paper but is a nightmare in practice.
SkiFire13 · 7 days ago
> The orphan rule. I get it, but it's still annoying. We can do better than newtype wrappers

By that do you mean that there are better alternatives that Rust could adopt or that we need such alternatives (but they could not exist)?

estebank · 7 days ago
One option would be to ignore the orphan rule withing workspaces.
j-krieger · 7 days ago
Huge agree with the orphan rule. We should be able to disable this in application crates, or do away with it when we can prove certain hygiene, like proc macros.
Animats · 7 days ago
“Smooth, iterative deepening”

The emphasis on cross-language compatibility may be misplaced. You gain complexity and lose safety. If we have to do that, it would help if it were bottom-up, replacing libc, for example.

Go doesn't do cross-language calling much. Google paid people to build the foundations, the low-level libraries right. Rust tends toward many versions of the basics, most flawed.

One of the fundamental problems in computing for about two decades now has been getting programs on the same machine to efficiently talk to each other. Mostly, people have hacked on the Microsoft .DLL concept, giving them state. Object request brokers, like CORBA, never caught on. Nor did good RPC, like QNX. So people are still trying to link stuff together that doesn't really want to be linked.

Or you can do everything with sockets, even on the same machine. They're the wrong abstraction, raw byte streams, but they're available.

ameliaquining · 7 days ago
My read, which is based on context from elsewhere but could be wrong, is that the post is not really talking about creating a replacement for traditional shared libraries that works across language boundaries (like COM or Wasm components). Rather, "other languages" here is a euphemism for C++ in particular, and the goal is to enable incremental migration of brownfield C++ codebases to Rust. The goal of pervasive memory safety everywhere, especially in the kinds of contexts where C++ has traditionally been used, requires some kind of answer to "what about all the existing programs?", and the current interop between Rust and C++ is not good enough to be a satisfying answer. If it gets improved to the point where the two languages can interoperate smoothly at the source level, then I think the possibility of Rust displacing C++ for most use cases becomes something like realistic.
zaphar · 7 days ago
Sometimes I think sockets with a spec for what's on the "wire" is about as good an abstraction as you can get for arbitrary cross-language calling. If you could have your perfect abstraction for cross-language calling what would it be?
Animats · 7 days ago
Not sure, but it should be message-oriented, rather than stream-oriented. You have to put a framing protocol on top before you can do anything else. Then you have to check that framing is in sync and have some recovery.

I'm currently struggling with the connection between Apache mod_fcgid (retro) and a Rust program (modern). Apache launches FCGI programs as subprocesses, with stdin and stdout connected to the parent via either pipes or UNIX local sockets. There's a binary framing protocol, an 8-byte header with a length. You can't transmit arbitrarily large messages; those have to be "chunked". There's a protocol for that. You can have multiple transactions in progress. The parent can make out of band queries of the child. There's a risk of deadlock if you write too much and fill the pipe when the other end is also writing. All that plumbing is specific to this application.

(Current problem: Rust std::io appears to not like stdin being a UNIX socket. Trying to fix that.)

01HNNWZ0MV43FF · 7 days ago
You could definitely do JSON / msgpack and have like 5 C API functions like, read, write, wake_on_readable, and it wouldn't be the worst thing, and it wouldn't incur any IPC overhead.
pron · 7 days ago
> You have to avoid abstractions and conveniences and minimizing allocations so as not to trigger the garbage collector.

That's not really how modern GCs work, and not how abstractions work when you have a good JIT. The latency impact of modern GCs is now often effectively zero (there are zero objects processed in a stop-the-world pause, and the overall CPU utilisation of the GC is a function of the ratio between the size of the resident set and the size of the heap) and a JIT can see optimisation opportunities more easily and exploit them more aggressively than an AOT compiler (thanks to speculative optimisations). The real cost is in startup/warmup time and memory overhead, as well as optimising amortised performance rather than worst-case performance. Furthermore, how much those tradeoffs actually cost can be a very different matter from what they are (e.g. 3x higher RAM footprint may translate to zero additional cost, and doing manual memory management may actually cost more), as brilliantly explored in this recent ISMM talk: https://youtu.be/mLNFVNXbw7I

> C++’s innovations in zero-cost abstractions

I think that the notion of zero-cost abstractions - i.e. the illusion of "high" [1] abstraction when reading the code with the experience of "low" abstraction when evolving it - is outdated and dates from an era (the 1980s and early '90s) when C++ believed it could be both a great high-level and great a low-level language. Since then, there's generally been a growing separation rather than unification of the two domains. The portion of software that needs to be very "elaborate" (and possibly benefit from zero-cost abstractions) and still low-level has been shrinking, and the trend, I think, is not showing any sign of reversing.

[1]: By high/low abstraction I mean the number of subroutine implementations (i.e. those that perform the same computational function) that could be done with no impact at all on the caller. High abstraction means that local changes are less likely to require changing remote code and vice-versa, and so may have an impact on maintenance/evolution costs.

dejw · 7 days ago
By claiming that jit sees more optimization opportunities what do you mean exactly? Jit is supplementary to aot
hyperman1 · 7 days ago
I've got a nice one recently: A pipeline has bytes being transformed to chars by a user selectable encoding, then the chars go to a next step.

In an AOT language, this must be dynamic dispatch, as there are multiple algorithms. The JDK, however, notices how the encoding is basically always UTF-8 and does an 'if utf8 then do utf8code else do dynamic dispatch'. Then the inliner comes along, pushes that if outside a loop, and merges the byte reading, encoding and char processing to 1 big code block, all optimized together.

pron · 7 days ago
Any optimisation transforms the program in some way that has to preserve its meaning. Generally, to do that, AOT compilers need to prove that the transformation is always correct; that can be difficult for some deep optimisations. OTOH, JITs can assume that some transformation is correct, aggressively apply it, and if it turns out they're wrong, the code will trigger some signal that will have the runtime deoptimise (some modern AOT compilers have some speculative and deoptimisation capabilities, but not as general as JITs').
codys · 7 days ago
When you say "modern GC", which particular implementation(s) of GC are you referring to?
adgjlsfhk1 · 7 days ago
Java has an excellent GC, but a horrible runtime. .net is probably the best GC integrated into a language with decent memory layout ability. If all you want is the GC without a language attached, LXR is probably the most interesting. it's part of MMTK which is a rust library for memory allocation and GC that Java, Ruby, and Julia are all in the process of adding options for.
pron · 7 days ago
ZGC

Deleted Comment

Deleted Comment

troupo · 8 days ago
The currently flagged comment and the discussion below it actually do have a salient point.

Comment: How about get an actual published language standard? How about get more implementations of it?

In the discussion, @infogulch: If you are aiming to be the foundation of an entire industry it doesn't seem unreasonable to ask for some specifications. https://news.ycombinator.com/item?id=44926375

I agree with @infogulch

woodruffw · 7 days ago
I think it’s flagged (although I don’t have an opinion on whether it should be) because it’s not clear at all whether it’s actually a reasonable thing to expect: plenty of industries are built on programming languages that don’t have formal specifications, much less natural language ones.

To argue by example: Ruby has an ISO standard, but that standard is for a very old version of the language. Python doesn’t have an independent standard at all; it’s like Rust in that the reference implementation is itself the standard. But nobody is champing at the bit to replace their Ruby or Python stack for standards reasons.

hitekker · 6 days ago
Rust's leadership wanted a formal specification. They thought it's reasonable. Hence, why they invested in a team to build a spec https://blog.rust-lang.org/inside-rust/2023/11/15/spec-visio...

One of the founding members wrote a blog post & RFC which gives some good reasons why a spec is needed https://blog.m-ou.se/rust-standard https://rust-lang.github.io/rfcs/3355-rust-spec.html

logicchains · 7 days ago
Nobody is champing at the bit to rewrite the OS stack in Ruby or Python.
testdelacc1 · 7 days ago
> How about get an actual published language standard?

I’m curious, did you Google before writing this comment? I did just now and the first result I found is https://github.com/rust-lang/reference.

This is something the Rust project is taking very seriously. You can read up on their approach in this detailed blog post from November 2023 - Our Vision for the Rust Specification (https://blog.rust-lang.org/inside-rust/2023/11/15/spec-visio...).

This is an ambitious and difficult project that is a work in progress. They’re continuing to work hard at it. There were 2 PRs merged to master less than 2 hours ago when I wrote this comment. On a Saturday, that’s commitment right there.

> How about get more implementations of it?

Here’s what another quick google search yielded. From the official Rust blog, published November 2024: gccrs: An alternative compiler for Rust (https://blog.rust-lang.org/2024/11/07/gccrs-an-alternative-c...)

Hope that helps dispel the confusion around the lack of spec or alternate implementation.

mjw1007 · 7 days ago
The plan described in "Our Vision for the Rust Specification", and the linked RFC3355, were abandoned early in 2024.

The team that was formed to carry out RFC3355 still exists, though it's had an almost complete turnover of membership.

They're currently engaged in repeating the process of deciding what they think a spec ought to be like, and how it might be written, from scratch, as if the discussion and decisions from 2023 had never happened.

The tracking issue for the RFC was https://github.com/rust-lang/rust/issues/113527 . You can see the last update was in 2023, at the point where it was time to start the actual "write the spec" part.

That's when it all fell apart, because most of the people involved were, and are, much more interested in having their say on the subject of what the spec should and shouldn't be like than they are in doing the work to write it.

jmull · 7 days ago
I hope this is clear: the published language standard the previous poster was asking about does not currently exist.

You linked to efforts to create a specification, but they aren't done or close to done.

It's also not clear the goal is to create a standard. E.g., according to the spec-vision document, the spec isn't planned to be authoritative (the implementation is). If that doesn't change the spec can't really function as a standard. (A standard would allow independent implementations and independent validation of an implementation. A non-authoritative spec doesn't.)

dgfitz · 8 days ago
As do I.

Because I’m a cynical person, I view LLMs like I’ve viewed rust for years: a neat tool that makes a small subset of people happy and a touch more productive.

The evangelical defense of either topic is very off-putting, to say the least.

@infogulch really nailed it with the comment about how the rust community wants influence/power, and ignores the responsibility that comes with said influence/power.

sitkack · 7 days ago
Python still has no standard and open antagonistic towards refactoring their standard library so that the language and the library aren't shipped as a monolith.

The most difficult part for alternative Python implementations is supporting the same stdlib.

jenadine · 7 days ago
Could you elaborate what "published language standard" exactly mean and what it would help for?
testdelacc1 · 7 days ago
I can’t speak for them, but here’s the Rust project’s rationale on writing a spec when they started on it: Our Vision for the Rust Specification (https://blog.rust-lang.org/inside-rust/2023/11/15/spec-visio...).

I do find it curious that the people who write comments demanding a spec and alternate implementations aren’t aware of the work-in-progress spec and alternate implementations.

voidhorse · 7 days ago
I don't have gripes with Rust per se, but I do take issue with the contingent of commenters that seem to not understand the value of a formal specification in the construction of formal computational systems in the first place. Basically every computational system lives and dies on its degree of formal specifiability, and having more formal specification is always better unless you are cool with being beholden to the fact that some particular implementation of some program happened to work at some point in time for some input once. That insecure ground is what you're standing on if you don't think formal specifications have any value. In the world of computation, the specification is the system, modulo incidental properties of implementation like performance.
aw1621107 · 7 days ago
> in the construction of formal computational systems

> Basically every computational system lives and dies on its degree of formal specifiability

How much software development work would you say qualifies as "construction of formal computational systems"? I feel like I have to be thinking of something different than you because to a first approximation I think ~no software has much in the way of formal specification done on it, if any at all.

I feel like there's a bit of black-and-white thinking here as well. It's not as if you pick either "full formal specification with bonus Lean/Coq/etc." or "YOLO" - there's shades of grey in between where you can nail down different bits with more or less specificity/formality/etc. depending on feasibility/priorities/etc. Same with support for/against a formal spec - there's more nuance than "an absolute necessity" or "a waste of time".

IshKebab · 7 days ago
> having more formal specification is always better

True, all other things being equal. But your logic falls down when all other things aren't equal.

Rust is a more "secure ground" than C even though C has an official specification and Rust doesn't really.

Also you shouldn't say "formal specification" in this context because I don't think you really mean https://en.wikipedia.org/wiki/Formal_specification

wolvesechoes · 7 days ago
People often cast doubts by asking why Rust needs a spec (spec is not the same as standard), and this proves there is still too little engineering in so-called "software engineering".
mollerhoj · 7 days ago
Software engineering is not engineering. No need to pretend that it should be. We dont rebuild a bridge 3 times before we get it right, but in SE thats a pretty good approach