Defining the Rust 2024 edition
In December, the Rust project released a call for proposals for inclusion in the 2024 edition. Rust handles backward incompatible changes by using Editions, which permit projects to specify a single stable edition for their code and allow libraries written in different editions to be linked together. Proposals for Rust 2024 are now in, and have until the end of February to be debated and decided on. Once the proposals are accepted, they have until May to be implemented in time for the 2024 edition to be released in the second half of the year.
Accepted proposals
Some proposals have already been accepted, the largest of which is a change to how functions (and methods) that use "Return Position impl Types" (sometimes also called "-> impl Trait") capture lifetime information. While this proposal is slated for inclusion in Rust 2024, it does leave certain questions open, and may end up not being included if those questions cannot be resolved in time. In Rust, one can write functions that return opaque types that can only be used by invoking methods of traits they implement. LWN covered previous work to improve the consistency of this feature late last year. An example of a function that uses Return Position impl Types is the following function definition that returns some type that implements the Foo trait, without committing itself to a specific type:
fn foo<'a, T>(x: &'a T) -> impl Foo { ... }
One wart with this functionality is the rule for how the inferred hidden type of the function incorporates other types from its surroundings. In the given example, the type that foo() returns can reference the type T. In Rust 2021, however, it cannot reference the lifetime 'a. This asymmetry requires users to introduce awkward contortions in order to correctly write functions that capture references and return them as part of an opaque type. This is especially troublesome in the case of trying to convert an asynchronous function to use the impl Trait syntax, because asynchronous functions can capture lifetime information in this way.
The proposal corrects this asymmetry in the 2024 edition by permitting lifetime
information and types to be captured using the same rules.
The discussion about this
feature proposal
included some concerns about whether this could lead to "overcapturing", where a
lifetime parameter that the user does not intend to be referenced is captured
anyway, restricting how the returned value can be used. The proposal ends up
leaving this question open, saying that these changes to the lifetime-capture
rules should not be included in the final
set of revisions for Rust 2024 unless the community can find "some solution
for precise capturing that will allow all code that is allowed under Rust 2021
to be expressed, in some cases with syntactic changes, in Rust 2024.
"
Two other accepted proposals have to deal with how
Cargo, Rust's package manager, handles dependencies.
One proposal changes how packages specify
optional dependencies. Currently, every
optional dependency of a package implicitly creates a Cargo "feature" —
Rust's solution for conditional compilation — with the
same name. The proposal says that this makes it "easy for crate authors
to use the wrong syntax and be met with errors
" and leads to "confusing
choices when cargo add lists features that look the same
",
because the names of optional dependencies appear alongside features that are
defined in the crate.
In Rust 2024, there will be no externally-visible feature with the same name as
the dependency by default. Instead, enabling an optional dependency will be done
using a feature named
"dep:package-name" instead of "package-name".
The other proposal that impacts dependency handling is designed to make it easier for Rust to automatically detect and warn about changes that may break semantic versioning compatibility guarantees. In current editions of Rust, when one crate depends on another, the first crate can expose types from the second crate as part of its API. The following example, taken from the proposal, shows code that re-exposes types from serde and serde_json:
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] pub struct Diagnostic { code: String, message: String, file: std::path::PathBuf, span: std::ops::Range<usize>, } impl std::str::FromStr for Diagnostic { type Err = serde_json::Error; fn from_str(s: &str) -> Result<Self, Self::Err> { serde_json::from_str(s) } }
If serde_json were to change the definition of serde_json::Error, then the return type of Diagnostic::from_str() would change, which could potentially be a breaking change. The proposal seeks to remedy this by turning an existing warning (lints.rust.exported_private_dependencies) into an error in the 2024 edition, and giving crates a way to mark dependencies that are deliberately exposed by adding a public flag to Cargo dependency declarations.
The last accepted proposal at the time of writing stands out by not modifying the language itself, but the policies around how future editions will maintain backward compatibility. Rust permits users to define syntactic macros (as opposed to procedural macros) that pattern-match given fragments of Rust syntax, and produce modified Rust code. When Rust adds new pieces of syntax, this impacts the meaning of existing macro definitions. This proposal seeks to clarify the policy around when and how the syntactic macro pattern-matching rules are updated to match new syntax, saying that when the syntax of Rust is changed in such a way that the patterns in syntactic macros no longer align with the actual grammar of the language that the project should: add a new pattern which preserves the behavior of the existing pattern; in the next edition, change the behavior of the original pattern to match the underlying grammar of the release of Rust corresponding to the first release of that edition; and have cargo fix replace all instances of the original pattern with the new pattern which preserves the old behavior.
Proposals under discussion
Currently, there is one unaccepted proposal that is in its final comment period before being approved, a proposal to add support to Cargo for respecting the minimum supported Rust version of a crate. Crates can already declare that they need a certain version of Rust using the rust-version field in the Cargo manifest. This proposal adds a new Cargo resolver that prefers versions of dependencies that have compatible rust-version fields.
Another proposal that has not yet been accepted, but that seems likely to be accepted because it has been part of Rust's overarching vision for some time, is a proposal that would reserve the gen keyword. The new keyword would be analogous to the async keyword, but permit the definition of generators — recently renamed in the internal documentation to coroutines to match use of the term outside of the Rust community — instead of asynchronous functions. It is already possible to write coroutines in nightly Rust, and they are part of how the compiler implements asynchronous functions, but they are not yet stabilized. The proposal doesn't suggest getting them stabilized by May, but only reserving the syntax so that automatic upgrades from Rust 2021 to Rust 2024 can update existing programs to avoid clashing with coroutines when they are introduced.
Migrating code from one edition to the next is always supposed to be possible using cargo fix. For newly reserved keywords, this means rewriting programs which use those keywords as identifiers. For example, "let gen = ...;" would be rewritten to "let r#gen = ...;". This uses a seldom-seen feature of Rust known as raw identifiers, which permit using otherwise reserved words as variable or function names by prefixing them with "r#".
The proposal prompted
a lot of debate about the exact place that this keyword would have in the Rust
parser, and whether it should be changed to reflect the new term coroutine,
eventually prompting Oli Scherer — the contributor who submitted
the proposal — to
declare: "This RFC is on hiatus until we have an
implementation and will then mirror the implementation
".
Work on an
implementation is ongoing.
Another proposal intended to make writing asynchronous code slightly smoother is
adding the
Future and
IntoFuture traits to the
prelude — the section of the standard library available without explicitly
importing a library. Alice Cecile
expressed doubts that this was as useful as
some of the existing functionality in the prelude, saying: "Generally I
find that traits are most useful in preludes when you want to implicitly use
their methods
". She observed that the only method that this proposal would
permit the use of is poll(). The proposal remains open for debate.
A proposal that hasn't seen much discussion since November is a suggestion to disallow directly casting function pointers to integers. Several supporters of the proposal noted that there are architectures where pointers to functions are different sizes than pointers to data, and expecting a usize value to contain a function pointer is incorrect. Others were concerned that changing this behavior would be making users jump through extra hoops for little benefit.
The last proposal currently under discussion would change how Rust represents ranges by adding new range types that would implement Copy, allowing them to be duplicated without boilerplate. The current range types do not implement Copy because of how this would interact with their use as iterators. The discussion of this proposal focused on how the introduction of new types might complicate the use of libraries that rely on the old types, for example by using the old types as indexes into collections. Peter Jaszkowiak, the contributor working on this proposal, did not believe this would be a significant problem, but said that he would adapt the proposal to include convenience features to ameliorate this if the language team thought that was warranted.
Conclusion
People who have been paying attention to Rust development since 2021 may find this list of changes for the 2024 edition a bit sparse, since it includes little mention of improvements to asynchronous code, to the standard library, or any of the hundreds of other small improvements Rust contributors have made in that time. These improvements are all already available in the 2021 edition. In Rust, only backward incompatible changes need to wait for the next edition. Therefore, the release of the 2024 edition heralds two things: the 2021 edition becoming more stable — in the sense that new syntax will now go in the 2024 edition — and the chance to fix various small issues that nonetheless hinder the continued evolution of the language, without breaking existing users.
Posted Jan 29, 2024 18:32 UTC (Mon)
by NYKevin (subscriber, #129325)
[Link]
To further explain this: The problem is that ranges implement Iterator ("a thing that you can iterate over") instead of IntoIterator ("a thing that you can convert into an Iterator"). Iterators generally don't want to be Copy, because for loops and the like would implicitly copy the Iterator rather than advancing it in-place, which can lead to confusion (e.g. you might expect for x in it.take(3){...} to advance the iterator to its fourth element, but if the iterator is Copy, then you advanced a temporary and not the original).
But this is maybe too narrow of a viewpoint - the real problem is that treating ranges as iterators is inherently confusing. Normally, we think of a range as a logical collection of objects (which are not all materialized). For example, the integer range [1, 5) (or 1..5 in Rust syntax) is loosely(!) equivalent to the array [1, 2, 3, 4]. It doesn't make sense for the integers to start disappearing as you iterate over it, but to some extent(!), that is what Rust does. In Python, range(...) and iter(range(...)) are two different objects. Python lets you iterate over a range as many times as you like, but you have to construct a new iterator each time (which the for loop syntax does implicitly if necessary, or you can do it manually if you want to fiddle around). Rust instead lets you iterate over a range once, and expects you to clone() or reconstruct it if you want to use it again. The proposal would make Rust behave more like Python in this regard, which IMHO is a significant improvement.
(Strictly speaking, Rust ranges do not have to be integer ranges. But if T is not Step, then Range<T> is not Iterator, and Step is only implemented for types that at least vaguely resemble integers, so the ranges that are relevant to this discussion are integer-ish ranges in practice.)
Posted Jan 29, 2024 19:14 UTC (Mon)
by bluca (subscriber, #118303)
[Link] (174 responses)
Posted Jan 29, 2024 19:32 UTC (Mon)
by tialaramex (subscriber, #21167)
[Link] (111 responses)
If you want to define a stable representation which does substantially more than C and put in the effort to keep that stable for some considerable time (certainly more than a few years) as the underlying Rust changes there's probably enthusiasm to actually add that. But what we tend to see from say C++ people is the fantasy that you can have C++-style comprehensive "everything just works" ABI freeze without the inevitable consequences of poorer performance and inability to fix obvious problems. If you actively _want_ worse performance and problems you can't fix, C++ already has those. They don't seem very happy about it though, "Break the ABI" is almost as popular as "Unspecialize vector<bool>" from the r/cpp crowds for example.
Posted Jan 29, 2024 20:37 UTC (Mon)
by draco (subscriber, #1792)
[Link] (55 responses)
Posted Jan 30, 2024 8:33 UTC (Tue)
by irishsultan (subscriber, #139189)
[Link] (54 responses)
Posted Jan 30, 2024 9:21 UTC (Tue)
by matthias (subscriber, #94967)
[Link] (53 responses)
In your example the compiler has to use the calling conventions from Edition 2024 for the function call and it has to compute the representation of the object using Edition 2018 rules. The compiler has to be able to produce code for each edition anyway for backwards compatibility.
There are other bigger issues:
Shared libraries are dated anyways. Nowadays many applications bring their private copy of shared libraries totally defeating the purpose of shared libraries. Many applications even bring their private copy of a JVM just to be sure that there are no compatibiliry issues.
Posted Jan 30, 2024 12:02 UTC (Tue)
by khim (subscriber, #9252)
[Link] (1 responses)
That's wrong. Generics are not templates. Monomorphisation is optional with generics and one may create one piece of code which would deal with all supported types (by receiving description of type as hidden argument). Swift does that and it works, but problem with that approach is the need to conjure these descriptors from thin air. It's possible to create Nothing impossible, really, but lots and lots of non-trivial work. Yet we don't see that in Java. Again: runtime type descriptors help. But, again, lots of non-trivial work.
Posted Jan 30, 2024 17:19 UTC (Tue)
by josh (subscriber, #17465)
[Link]
If we provide such a mechanism, it'd be an opt-in that libraries can use to export an interface containing a generic without requiring downstream consumers of that interface to monomorphize it into their own code. And that'll still be sufficient for people to export a shared library interface, while not *forcing* them to use that mechanism.
Posted Jan 30, 2024 13:00 UTC (Tue)
by LtWorf (subscriber, #124958)
[Link] (50 responses)
The structures do not change that often when bugs (like security bugs) get fixes. Anyway the changes that would require recompilation are fewer.
> Shared libraries are dated anyways.
And that is 100% the fault of languages like go and rust. How much extra effort is required to rebuild everything because there was a bugfix in the stdlib?
Posted Jan 30, 2024 15:07 UTC (Tue)
by matthias (subscriber, #94967)
[Link] (49 responses)
Why is rust at fault if software distributers choose to vendor shared libraries (which are obviously non-rust) or complete jvms? With the exception of the most basic system libraries
> How much extra effort is required to rebuild everything because there was a bugfix in the stdlib?
How much effort is required if the bug is fixed in the STL? Or if there is a bugfix in any C++ library? There is the choice of recompiling everything against the newesr version of the library which might be API compatible but not ABI compatible or backporting the fix to some older version that is ABI compatible and hoping that the fix itself does not break ABI compatibility (usually it wont). But backporting is not cheap. It costs programmer time, while recompilation only costs machine time.
Posted Jan 30, 2024 16:06 UTC (Tue)
by bluca (subscriber, #118303)
[Link] (30 responses)
This is 100% a fallacy. It works in theory, not in practice. In reality, "rebuild the whole universe every time someone sneezes" requires an inordinate amount of engineering time to set up and maintain. It's just that the cost is pushed away from the rust core developers, so they can happily ignore it and have someone else shoulder it.
Posted Jan 30, 2024 22:55 UTC (Tue)
by Cyberax (✭ supporter ✭, #52523)
[Link] (29 responses)
Not really? It does require some work, but at this point it's an old hat. Heck, some people are using Nix and Gentoo that do that all the time.
Posted Jan 31, 2024 10:03 UTC (Wed)
by LtWorf (subscriber, #124958)
[Link] (3 responses)
Posted Jan 31, 2024 10:19 UTC (Wed)
by matthias (subscriber, #94967)
[Link] (2 responses)
Posted Jan 31, 2024 10:36 UTC (Wed)
by LtWorf (subscriber, #124958)
[Link] (1 responses)
Time is a limited resource.
Posted Jan 31, 2024 10:54 UTC (Wed)
by dezgeg (subscriber, #92243)
[Link]
Posted Jan 31, 2024 11:34 UTC (Wed)
by bluca (subscriber, #118303)
[Link] (24 responses)
Posted Jan 31, 2024 14:10 UTC (Wed)
by foom (subscriber, #14868)
[Link] (1 responses)
Posted Jan 31, 2024 15:49 UTC (Wed)
by bluca (subscriber, #118303)
[Link]
Posted Jan 31, 2024 14:29 UTC (Wed)
by mb (subscriber, #50428)
[Link] (17 responses)
Which would not have been a CVE if it had been implemented in Rust.
Posted Jan 31, 2024 15:46 UTC (Wed)
by bluca (subscriber, #118303)
[Link] (10 responses)
Posted Jan 31, 2024 15:58 UTC (Wed)
by mb (subscriber, #50428)
[Link] (9 responses)
Posted Jan 31, 2024 17:54 UTC (Wed)
by LtWorf (subscriber, #124958)
[Link] (8 responses)
Posted Jan 31, 2024 18:45 UTC (Wed)
by Cyberax (✭ supporter ✭, #52523)
[Link] (2 responses)
PDF: https://github.com/pdf-rs/pdf (with automated fuzz tests)
Rust ecosystem is already far surpassing the quality of even the old C-based libraries.
Posted Jan 31, 2024 19:05 UTC (Wed)
by LtWorf (subscriber, #124958)
[Link] (1 responses)
Posted Jan 31, 2024 19:27 UTC (Wed)
by Cyberax (✭ supporter ✭, #52523)
[Link]
The new ecosystem in modern languages is already outpacing the traditional creaky old C-based infrastructure, that is mostly held together by duct tape and wishful thinking. So new languages are clearly doing something right (and some things wrong, of course) and perhaps you should actually look at what they're doing? Instead of just mandating that cars should have a flagman walking ahead of them to make sure they don't scare horses.
Posted Jan 31, 2024 18:50 UTC (Wed)
by mb (subscriber, #50428)
[Link] (4 responses)
https://security.googleblog.com/2022/12/memory-safe-langu...
> Android 13 is the first Android release where a majority of new code added to the release is in a memory safe language.
Posted Jan 31, 2024 19:04 UTC (Wed)
by LtWorf (subscriber, #124958)
[Link] (3 responses)
As you know perfectly well.
Posted Jan 31, 2024 19:12 UTC (Wed)
by mb (subscriber, #50428)
[Link] (1 responses)
And I didn't say that.
Do you understand what zero means in denominators?
Posted Jan 31, 2024 19:13 UTC (Wed)
by mb (subscriber, #50428)
[Link]
Posted Jan 31, 2024 19:43 UTC (Wed)
by Wol (subscriber, #4433)
[Link]
Who cares. Classic "lying with statistics".
If you want to argue that Rust has fewer bugs because it has fewer lines of code, that may be true. But that's not the argument being made.
The argument is that Rust has fewer bugs per KLoc. Which given that C has had $deity knows how many years to get rid of bugs, is rather telling.
Especially if Rust's "fewer" bugs is ZERO bugs.
Cheers,
Posted Jan 31, 2024 17:51 UTC (Wed)
by LtWorf (subscriber, #124958)
[Link] (3 responses)
A double free() isn't the only way to generate a CVE… the argument makes no sense and is making you appear like a fanboy.
Posted Jan 31, 2024 18:48 UTC (Wed)
by mb (subscriber, #50428)
[Link] (2 responses)
So?
>They would however generate a CVE.
Why? Garbage in, garbage out.
>is making you appear like a fanboy.
So?
Posted Jan 31, 2024 19:02 UTC (Wed)
by LtWorf (subscriber, #124958)
[Link] (1 responses)
So it's a "denial of service", you can ask a CVE number for that.
Say that I'm in an online game and I can crash the server making everyone else unable to play… that's a security bug, whatever your personal feelings might be.
> A program termination on garbage input is perfectly safe and sane.
No it isn't. It is better than jumping to whatever memory and running, but isn't what software should be doing, no.
> So?
I'm sure you can find out what the word "fanboy" means on your own :)
Anyway since you are so intent in denying the obvious, and I'm an atheist and don't really accept faith arguments, feel free to continue on your own.
Posted Jan 31, 2024 19:08 UTC (Wed)
by mb (subscriber, #50428)
[Link]
Did you even read the CVE we are talking about?
Posted Feb 3, 2024 1:16 UTC (Sat)
by jschrod (subscriber, #1646)
[Link] (1 responses)
This is not worthy to discuss.
*plonk*
Posted Feb 3, 2024 9:36 UTC (Sat)
by mb (subscriber, #50428)
[Link]
Really? Nobody said that in this whole discussion.
But you know what? You are actually right.
But it is no coincidence that these classes of bugs make the majority of security bugs.
And that is what makes Rust code have *almost* no security problems by definition.
Posted Jan 31, 2024 15:37 UTC (Wed)
by Cyberax (✭ supporter ✭, #52523)
[Link] (3 responses)
And yes, binary diffs are a thing. And static linking can be made patch-friendlier.
Posted Jan 31, 2024 18:46 UTC (Wed)
by LtWorf (subscriber, #124958)
[Link] (2 responses)
and the rust stdlib is smaller than the C one.
Posted Jan 31, 2024 18:52 UTC (Wed)
by Cyberax (✭ supporter ✭, #52523)
[Link]
Basically, pretty much every function in glibc would be a CVE within this list.
Posted Jan 31, 2024 18:55 UTC (Wed)
by mb (subscriber, #50428)
[Link]
Yes, Rust has bugs, too.
Rust does a CVE for every released unsoundness bug. (Please google for it, if you don't know what unsound means). They take their job seriously.
That doesn't mean these are actual security problems, though. (google "rust soundness", if you don't understand why)
Posted Feb 3, 2024 1:13 UTC (Sat)
by jschrod (subscriber, #1646)
[Link] (17 responses)
And that is, in a nutshell, why DevOps is so important.
It teaches developers the problem that operation has with such fantasies.
Posted Feb 3, 2024 9:19 UTC (Sat)
by mb (subscriber, #50428)
[Link] (16 responses)
It is possible to have dynamic libraries with Rust.
It is just not possible to have a stable ABI for *arbitrary* programs. That just as impossible for Rust as it is impossible for C++ and even C.
But if you fear world rebuilds as an issue for your library, you *can* avoid that by carefully making it a shared library. No different from C++ or C.
The reality is that these world rebuilds rarely are an issue.
Even if a CVE is assigned to a certain bug in a central library, it rarely affects any *actual* program. In Rust programs CVEs are assigned for all unsoundness bugs. An unsoundness bug *could* result in an invalid program iff the program exploits the unsoundness. But in practice that rarely happens, because usually such code would not be idiomatic Rust code.
Lots of really bright people who wrote great software are commenting here. I really appreciate that and it makes LWN kind of unique.
Posted Feb 3, 2024 12:22 UTC (Sat)
by pizza (subscriber, #46)
[Link] (13 responses)
The entire C world disagrees with you, using the fact that it _exists_ as evidence.
Posted Feb 5, 2024 10:07 UTC (Mon)
by farnz (subscriber, #17727)
[Link] (12 responses)
I don't believe that the C world has a stable ABI for arbitrary programs; rather, the C language is sufficiently limited that it's easy to avoid the sorts of things that result in an ABI change.
Otherwise, you're claiming that if I reduce MAX_BYTES in the below header file and rebuild the dynamic library that used it, any program that was built against the old definition (and hence the old ABI) will automatically adjust to match:
This does not match the C standard I'm used to working with.
Posted Feb 5, 2024 13:17 UTC (Mon)
by pizza (subscriber, #46)
[Link] (11 responses)
You changed the *API*, of course you're going to have issues. This is true of _any_ language, static or dynamic; computer or human.
Meanwhile, in the C world, I can take a shared library that is literally over two decades old (and its accompanying header file, which desctives the API) and expect it to JustWork(tm) with my brand-new software and the latest compiler and libc for my platform [1] That's what "Stable" refers to in this context.
But going back to your example *API*, it's not well thought out. But defining "good" APIs is a discipline all of its own.
[1] To be fair, some of that credit is due to glibc's commitment to backwards compatibility.
Posted Feb 5, 2024 14:19 UTC (Mon)
by farnz (subscriber, #17727)
[Link] (5 responses)
I did not change the API at all - and in a statically linked world, everything works just fine. The API remains (both before and after the change) "you must not supply data in chunks larger than MAX_BYTES"; if I supply you a .a and matching .h for that library, you can statically link, and a simple recompile will fix things if you obey the API as documented (e.g. by reading in at most MAX_BYTES at a time via read(2), then supplying them to this library).
If I was committing to a stable ABI, I'd use a version script (as glibc does) to provide both old and new versions of the symbols, and to do whatever it takes to handle both old and new versions with the same underlying algorithms. This is a lot of work, and it's to the glibc maintainers' credit that they do this work, so that even if they change APIs (not just ABIs), things Just Work.
Part of the problem here is that you've internalised a whole pile of rules around ABI stability, and you're assuming that they're part of the C language - but they're not, they're things that you have to do to have a stable ABI even in C. It's just that in Rust (and C++), the things you have to do to have a stable ABI are much more visibly painful than they are in C, because there's not many constructs in C that don't translate directly to the ELF psABI for your platform (const is one, the preprocessor is another, I can't think of a third off the top of my head). This, in turn, is because the C language simply doesn't have useful features (like generics) which aren't directly representable in ELF psABIs (in part because the ELF psABIs themselves aim to fully define the ABI for a C-like language, and not for something more capable).
Posted Feb 5, 2024 16:23 UTC (Mon)
by pizza (subscriber, #46)
[Link] (4 responses)
You changed the definition of MAX_BYTES, which is a change to the API -- No ifs, buts, or hand-waveys.
That said, not every change to the API necessarily represents a change to the ABI (for existing stuff), and there are ways to design the APIs to make your example (ie changing the definition of MAX_BYTES) have no effect on the ABI. Just off the top of my head:
1) Give the structure an explicit data length field and use a variable-length structure. This also means one doesn't get to use sizeof(structure) or static allocations.
For example, the decades-old BSD socket API uses a combination of (1) and (2). I can take a binary that was only ever aware of IPv4 addressing (32-bit addresses) and use it with a library that is aware of IPv6 (128-bit addresses), and it will work. Not because of glibc's fancy symbol versioning, but because the API was crafted with care to ensure that additions wouldn't change the ABI for the older stuff.
Another way of looking at this is that the shared library boundary is akin to a network protocol; a change in MAX_BYTES in your strawman example means the over-the-wire data format will also change, meaning you have to update both sides in lockstep for nearly any change, but with care (eg adding an explicit length field) you can check for a size greater than [your idea of] MAX_BYTES and handle/fail it gracefully.
Posted Feb 5, 2024 16:46 UTC (Mon)
by farnz (subscriber, #17727)
[Link] (2 responses)
No, MAX_BYTES's value is not part of the API; it's part only of the ABI. The fact that you don't give reasoning for why the value of a constant (as opposed to the constant's name) is part of the API implies that you have an internalised view of API that you define as "the things that affect the C ABI", at which point your whole argument is circular.
As an application programmer, following the published Application Programming Interface, I don't care what value MAX_BYTES takes; I just know that I must use the named constant MAX_BYTES to refer to it, and my application will do the right thing across the interface. It's not until you build a binary that you refer to a concrete value; and, indeed, if C was a more capable language, my header file equivalent would only tell you that there is a constant MAX_BYTES, but you'd not get the value of that constant until you combined that with an implementation. It's just that C lacks encapsulation, so when I tell you in my API that there is a named constant, I also have to tell you what value to use for it - I can't hide that from you.
And your "network protocol" example is exactly why C does not have a stable ABI by default - you have to carefully define your ABI in order to avoid problems, and the only "advantage" C has over Rust in this respect is that the things that you use to define your C ABI are also the full power of the C language, whereas in Rust, they're what you get when you remove significant features (like monomorphized generics) from the language.
Posted Feb 5, 2024 16:57 UTC (Mon)
by pizza (subscriber, #46)
[Link] (1 responses)
Sure. And then you went and arbitrarily switched from the library-provided MAX_BYTES symbol to one of your own creation/definition, and complained that it caused problems.
You don't get to arbitrarily change the local definition of something and expect to successfully interoperate/communicate/whatever with something else.
Posted Feb 5, 2024 18:05 UTC (Mon)
by farnz (subscriber, #17727)
[Link]
No I did not - I changed the library-provided MAX_BYTES, and rebuilt the library with a smaller version. If the library ABI was stable (as would be the case if C provided a stable ABI), then I'd find that applications built for the older version of the API would work with the newer library. As it is, by rebuilding the library with an ABI change (but not an API change), I've broken all applications that were written against the old DSO, but now dynamically link against the new DSO.
And this is the point - I've done a change that's local to the library (the library's API does not change), that breaks the library ABI, and yet the C language doesn't do anything to stabilize that ABI. You're trying to declare this as somehow "out of bounds" because it shows up that C's ABI is also unstable, unless you take care at the library level to also keep a stable ABI.
Once you're carefully doing a stable ABI for your library, then Rust and C provide similar tools - you can define your ABI in terms of the things that the ELF psABIs (and other platform equivalents) provide, and you know that it's a special module that you need to be careful with. The only difference is that most of C is stuff that lives in the psABI for your OS, but most of Rust does not, and thus when you write your ABI module in Rust, you find yourself feeling much more restricted than you would be if you were writing C.
But this feeling is not an advantage of C - it's that C is sufficiently impoverished as a language (AFAICT, there's nothing in C that wasn't invented by 1960) that you don't have much that isn't already in the psABI for your platform. And that goes double for ELF platforms, since the ELF psABI was defined in terms of the (already well-understood) needs of ld for C and FORTRAN 77, not for anything more modern.
Posted Feb 5, 2024 16:51 UTC (Mon)
by mb (subscriber, #50428)
[Link]
Posted Feb 5, 2024 16:11 UTC (Mon)
by mb (subscriber, #50428)
[Link] (4 responses)
Rust does have a stable C interface.
Please compare apples to apples.
>But going back to your example *API*, it's not well thought out.
Yep. That's the thing here. To have a stable ABI you have to constrain your API. That is true for Rust, that is true for C++ (d-pointer, anyone?) and that is also true for C (#define). Thou shalt not modify the #defined values is one of the rules that you applied.
It will probably happen that a non-generic subset of Rust will be defined as stable ABI in the future.
But I think today is not the time to do that. We should stabilize the APIs a bit more, before we stabilize a subset of the ABIs. Due to the inter-crate compatibility guarantee between editions, we already have a big constraint on what editions can change. I think it's not the time to constrain that further, yet. Just use a C ABI with simple types, if you need a stable ABI.
Defining a stable ABI for the whole language probably is impossible.
Posted Feb 5, 2024 16:32 UTC (Mon)
by pizza (subscriber, #46)
[Link] (3 responses)
Well, duh. That the entire point; to get a stable ABI in Rust today, you have to essentially demote it to what C provides, on both the library provider and library user, even if both are written in Rust.
> It will probably happen that a non-generic subset of Rust will be defined as stable ABI in the future.
"probably in the future" is not something that someone [considering] using Rust today should ever plan on happening.
Posted Feb 5, 2024 16:44 UTC (Mon)
by mb (subscriber, #50428)
[Link] (1 responses)
Which makes it no less stable or usable for stable ABIs than C.
We need stable Rust ABIs, because C has a stable ABI?
> "probably in the future" is not something that someone [considering] using Rust today should ever plan on happening.
What should they do instead? Use C? How does that make any sense? Rust has a C ABI.
Posted Feb 5, 2024 17:10 UTC (Mon)
by pizza (subscriber, #46)
[Link]
If you want/need a stable ABI with Rust, you have to present (and/or consume) a C ABI, because because Rust is unlikely to gain a "Stable Native Rust" ABI in the foreseeable future.
Posted Feb 5, 2024 16:52 UTC (Mon)
by farnz (subscriber, #17727)
[Link]
We have to demote to less than what C provides, actually - we have to demote down to the things that are provided by the platform ABIs (Win32, Win64, ELF psABIs, mach-O), and we have to do that in both cases, since there are things in C that are not represented in the platform ABIs, either.
The difference is that much, much more of useful Rust is not directly represented in the platform ABIs, because there's a much bigger chunk of Rust that's more powerful than the platform ABIs directly provide (not least because the platform ABIs originally intended to cover most useful C and FORTRAN code, so were built to provide most of what C needs). But if you want a stable ABI with Rust, you can do it; it's just more obvious how much you're throwing away to get a stable ABI.
Posted Feb 3, 2024 12:24 UTC (Sat)
by pizza (subscriber, #46)
[Link] (1 responses)
They are if you don't have the complete source code to _everything_.
Which, when you are consuming commercial/proprietary libraries, you often (if not usually) don't.
Posted Feb 20, 2024 15:05 UTC (Tue)
by natkr (guest, #123377)
[Link]
Posted Jan 29, 2024 21:37 UTC (Mon)
by quotemstr (subscriber, #45331)
[Link] (10 responses)
100% correct. Inter-DSO APIs should be specified in explicitly ABI-stable language-neutral API (e.g. gobject), not the mangled artifacts of one's language of choice.
If I could wave a magic wand, I'd outright ban any mangled symbol appearing in a dynamic symbol table.
Posted Jan 30, 2024 17:21 UTC (Tue)
by josh (subscriber, #17465)
[Link] (9 responses)
Posted Jan 30, 2024 17:37 UTC (Tue)
by quotemstr (subscriber, #45331)
[Link]
Have an ABI stable API? Spell it using gobject or whatever. Mangled names aren't ABI stable anyway (or, for the sake of language flexibility, should not be). Don't need an ABI-stable API? Statically link and gain the benefits of LTO.
Putting ABI-stable high level APIs across DSO boundaries is the worst of both worlds.
Posted Jan 31, 2024 13:33 UTC (Wed)
by farnz (subscriber, #17727)
[Link] (7 responses)
That's the point, though - for a stable ABI, you should be forcing yourself to consider every exported item with care, and deciding whether or not it should be part of the stable ABI, or whether this should be part of the per-language stable API (that might call a stable ABI function, but is statically linked to your program).
After all, this is how dynamically linked libraries already work in C today on ELF systems (just like they did on SunOS 4.0 in 1988); you have a .so that's the DLL itself, and is dynamically linked. You have a .lib that's statically linked, and that mostly tells ld to put references to the DLL in place. And you have .h (and in some edge cases .c) files that the consumer adds to their set of sources and actually builds against. The idea is that the person who creates the DLL has considered what parts of it are sensible to dynamically link, and what parts must be statically linked, and has carefully planned out the stable ABI for the dynamically linked component; distros have kinda broken this model by saying "well, actually, all symbols can be dynamically linked, even if there's no stable ABI, and we will make someone's life miserable if you don't ensure that all symbols from your library have a stable ABI, even if they were ones you never intended to export at all".
Posted Jan 31, 2024 14:52 UTC (Wed)
by paulj (subscriber, #341)
[Link] (5 responses)
https://docs.oracle.com/cd/E19641-01/
Glibc has been using symbol versioning for a long time to support multiple incompatible ABIs within the same library - allowing old and new applications to be supported without breakage, without DLL versioning hell.
If you write a library, and you write a map to describe your ABI, and maintain it, C and ELF gives you powerful tools to maintain compatibility, seamlessly. Since the 90s.
Posted Jan 31, 2024 15:22 UTC (Wed)
by farnz (subscriber, #17727)
[Link] (4 responses)
Yes. But the vast majority of libraries don't do that - they are designed to be statically linked and never swapped out, and distributions "cheat" by using one version of the header file and a different version of code, and getting very upset (see also "unvendoring" of sources) when upstreams don't comply with their demand that everything be designed for dynamic linking with a stable ABI.
Looking at the stuff I have installed, glibc is the only library that goes the full mile on providing a stable ABI; several others (glib, for example) clearly limit the exported symbols to a set that they're likely to support, but don't have symbol versioning, but quite a few commonly used libraries (libz for example) look like they just export everything as-if I was supplying them to my code as source.
And this is a large component of the problem; for C, the "correct" way to do it comes from Solaris 2.5 in 1995, where you supply a dynamic library where all symbols are versioned and thus supply a stable ABI, along with a combination of C source and static libraries that supply a stable API implemented atop the stable ABI of the dynamic library. But the "default" is that unless you do the work to do things properly, every non-static symbol in your C library becomes part of your stable ABI whether you like it or not; if the default was flipped round to "nothing is exported unless you supply a version script", then we'd be in a different place for these discussions, since it would be very clear that much of the C ecosystem doesn't actually have a stable ABI either (since you need a stable API to build a stable ABI on top of).
Posted Jan 31, 2024 15:36 UTC (Wed)
by joib (subscriber, #8541)
[Link] (2 responses)
Posted Jan 31, 2024 16:02 UTC (Wed)
by paulj (subscriber, #341)
[Link]
Also, it was not hard to check and control visibility of symbols even prior to symbol versioning. Checking is just nm. A little sanity-check script to dump your libraries symbols and diff them to what's intended to be there is... trivial. The Solaris 2.4 linker guide has examples on editing linker symbols.
It's just a culture of refusing to use long-standing tooling.
It was not hard in Sun to just learn what you had to do - with the exact same tools as Linux has had since almost same time. What Sun had was technical leadership willing to make legions of engineers bashing out code take a stap back and spend 5 minutes a day checking what symbols they'd added / modified. The FOSS world lacks that leadership.
Posted Jan 31, 2024 16:17 UTC (Wed)
by farnz (subscriber, #17727)
[Link]
Sure, and C relies on documentation and skilled developers to avoid making mistakes, but that's the C culture. At least with the defaults set correctly, it becomes easier to avoid making the mistake - you mark the exported symbols in the source code, and review can catch changes to exported symbols (but not implementation details).
Posted Jan 31, 2024 15:56 UTC (Wed)
by paulj (subscriber, #341)
[Link]
;)
I think it's ridiculous. It's not that hard to maintain C interface mapfiles. It's a socio-cultural problem in FOSS - just ignorance for most, but willful, reckless, disdain even just for /managing/ stability in some cases (I've fallen out with a former co-maintainer in small part over this). [note, managing stability could mean no more than just documenting "This bit is stable, all the rest is free-for-all"].
Posted Feb 2, 2024 6:51 UTC (Fri)
by josh (subscriber, #17465)
[Link]
That's not the same issue I was raising, though. I *absolutely* think that you should consider every part of your public ABI with care. However, that does *not* mean I want every public ABI to have to use raw pointers, eschew things like Option and Result and String and Vec and HashMap, be unable to provide objects that have methods, be unable to accept parameters that can be anything-that-implements-a-trait, and many many other features.
crabi will solve some of this for cross-language ABIs, and there's another effort to provide a much broader stable ABI surface that's just for Rust-to-Rust calls, but in both cases it'll take a while before we get there. And in the meantime, I don't think it's reasonable to require that people provide a C-only ABI just so it can be a shared library; that'd be a substantial step backwards.
Posted Jan 29, 2024 22:43 UTC (Mon)
by pizza (subscriber, #46)
[Link] (43 responses)
Excuse me? With Rust, you can't fix *anything* unless you have the source code to *everything*. You are at the complete mercy of your software vendors. [1]
Now one can reasonably argue that this "requiring the complete corresponding source code to _everything_" consequence is actually a GoodThing(tm), but let's not pretend that the world solely consists of F/OSS.
[1] Unless the vendor Rust libraries have defined a stable C-ish ABI, and your Rust application uses them as external C libraries rather than Rust-native.
Posted Jan 30, 2024 0:06 UTC (Tue)
by Cyberax (✭ supporter ✭, #52523)
[Link] (41 responses)
Posted Jan 30, 2024 9:01 UTC (Tue)
by gspr (guest, #91542)
[Link] (39 responses)
Posted Jan 30, 2024 12:05 UTC (Tue)
by khim (subscriber, #9252)
[Link] (38 responses)
It actually have a lot of costs, too. And it's entirely not clear why people who don't need that ability have to pay these costs if they don't benefit from said ability.
Posted Jan 30, 2024 12:18 UTC (Tue)
by bluca (subscriber, #118303)
[Link] (6 responses)
Posted Jan 30, 2024 12:48 UTC (Tue)
by khim (subscriber, #9252)
[Link] (5 responses)
AFAICS said community is like a Java community, Python community or any other language community. As in: if distributions want to do something — they may do the work and present the results for discussion, if distributions don't want to do any work — then it's not Rust fault. They are ambivalent. If distributions want to ship something and, more importantly, want to support something — then it's fine, if they want to dump support on the mainstream community — then it's less than fine. After all when some rust package breaks in Android you don't go and ask on Rust forums about how would you go and fix Soongs
Posted Jan 30, 2024 13:10 UTC (Tue)
by LtWorf (subscriber, #124958)
[Link] (4 responses)
You being unaware of the work being done is not the same as no work being done.
Posted Jan 31, 2024 10:29 UTC (Wed)
by matthias (subscriber, #94967)
[Link] (3 responses)
Rust should be able to use a global cache for build objects. So there should be no need to compile the same code twice. If you build statically linked C-code, you also reuse the precompiled libraries and do not recompile the libraries for every executable you build.
Posted Jan 31, 2024 10:32 UTC (Wed)
by mb (subscriber, #50428)
[Link] (2 responses)
Posted Jan 31, 2024 10:50 UTC (Wed)
by Wol (subscriber, #4433)
[Link] (1 responses)
In other words, the source may be a library, but the object most definitely isn't.
So what we clearly need - Cyberax's comments about binary libraries aside - is a manifest which says "this is the ABI of this library" and declares all the types it exports.
I can see various problems with this approach - types defined in the user program, call-backs, etc. But it will satisfy a LOT of dynamic linking requirements, for a lot of libraries. Probably not all ... (sounds to me like those libraries I've seen mentioned that are just a dot-h).
Cheers,
Posted Jan 31, 2024 14:21 UTC (Wed)
by mb (subscriber, #50428)
[Link]
I think that's not that much different from C++ templates, except that Rust adds strict constraints.
But I must say that I don't know how all this currently works in Rust dylibs.
> I can see various problems with this approach - types defined in the user program, call-backs, etc.
constraints will restrict what types/traits can be used.
Posted Jan 30, 2024 13:07 UTC (Tue)
by LtWorf (subscriber, #124958)
[Link] (30 responses)
Do you think there are many gnu/linux users who do not use distributions?
Isn't a distribution a common enough use-case that we should keep it into account? Since it accounts for roughly 100% of the users.
Posted Jan 30, 2024 13:35 UTC (Tue)
by khim (subscriber, #9252)
[Link] (29 responses)
Sure. It's not as if I have a choice: C/C++ world have never managed to create useful enough packaging tool (there are Vcpkg and Conan but there are just way too many packages that are not available in either of them and, worse, many C/C++ libraries developers don't like when you use these). But these days I tend to just import the majority of libraries via Why should we? C and C++ are weird exception, most other, more-or-less modern languages have much better alternatives. At least they don't force you to learn different commands for the exact same thing if you develop for different platforms. What kind of joke is that? The majority of development happens on Windows and any solution that doesn't adequately work on Windows is just simply non-starter. And MacOS support is nice to have, too. That's why even C/C++ world dropped autotools and switched to CMake. Which is also awful (in some sense even more awful, that autotools), but at least it's cross-platform. Cargo is also cross-platform and I don't see how people would go from there to distros (except when forced by some rules in government contract or something like that). Tomorrow… tomorrow situation maybe different. Who knows, maybe supernova explosion would make half the Earth sterile and thus would halt Windows development. Then distributions may become more impoertant and would even gain 100% of users. But today… today it's Windows, MacOS, Linux. In that order. And Linux, for the majority, is WSL.
Posted Jan 30, 2024 16:08 UTC (Tue)
by bluca (subscriber, #118303)
[Link] (6 responses)
Posted Jan 30, 2024 17:07 UTC (Tue)
by khim (subscriber, #9252)
[Link] (5 responses)
Sure. Linux ( Why would they they do that? You only would need to do that if Linux would be demoted to Tier2 (or Tier3). But to make that ploy with distributions work you have to remove Tier1 status from Windows/MacOS and that would be bad idea. Autotools tried that and are now considered “legacy” by most C/C++ developers.
Posted Jan 30, 2024 20:12 UTC (Tue)
by bluca (subscriber, #118303)
[Link] (4 responses)
Posted Jan 30, 2024 20:47 UTC (Tue)
by khim (subscriber, #9252)
[Link] (3 responses)
You either support all Tier1 platforms identically (which, actually, makes them Tier1 platforms) or you embrace the differences and then one of them becomes Tier1 and all others become Tier1.1 or Tier1.5. And, well… Linux distros… All other popular platforms offer an SDK and the ability to install binary, compiled with the use of said SDK, but in case of Linux “wget | bash” is the closest thing to that (note that there are not And, well… if there are no better options then this one would be chosen. It's as simple as that.
Posted Jan 31, 2024 11:35 UTC (Wed)
by bluca (subscriber, #118303)
[Link] (2 responses)
Posted Jan 31, 2024 12:04 UTC (Wed)
by khim (subscriber, #9252)
[Link] (1 responses)
How may it work, otherwise? Support tier is quite literally defined by the set of things guaranteed to work on a given platform. Here are the rules. If different platforms are treated differently then how may you even claim they all are “first class”? What does “first class support” even mean if you provide different guarantees for different “first class” targets? How can you justify calling that “first class support” if things are not treated the same?
Posted Jan 31, 2024 16:43 UTC (Wed)
by Wol (subscriber, #4433)
[Link]
Because all platforms are not the same?
Women and men do not like being treated "the same" - treat a woman like a man and you'll get the cold shoulder or a slap round the face!
First class support means they are treated FAIRLY. If two platforms provide different support TO YOU, you cannot treat them THE SAME back. It's just not possible. But you can still treat them fairly.
We know what you're trying to say. But arguing "ad extremis" just hurts your own cause!
Cheers,
Posted Jan 30, 2024 22:40 UTC (Tue)
by LtWorf (subscriber, #124958)
[Link] (21 responses)
You want to make me believe that 99% of the software present on your computer doesn't come from distribution packages?
For people who aren't yourself, your software isn't any more important than all the other software they use. Possibly it is even less important. They don't want to manually update your software specifically. They want autoupdates, default configurations, and so on.
You are quite uninformed about autotools. The whole point of it is to have portability. cmake covers a lot of common cases, but you might be in a situation where cmake lacks some features that autotools have. I've never encountered a feature that cmake has and autotool doesn't have.
People like repositories… what they don't like is to treat every single program as a special butterfly. In that case they will just not use it, unless it's needed for work, in which case they will put up with the pain.
Posted Jan 30, 2024 23:17 UTC (Tue)
by Cyberax (✭ supporter ✭, #52523)
[Link] (13 responses)
Autotools don't work on native Windows and barely work on macOS. But yeah, they would allow you to run your application on HP-UX from 1995!
Posted Jan 31, 2024 10:16 UTC (Wed)
by LtWorf (subscriber, #124958)
[Link] (12 responses)
Posted Jan 31, 2024 15:41 UTC (Wed)
by Cyberax (✭ supporter ✭, #52523)
[Link] (11 responses)
Posted Jan 31, 2024 17:48 UTC (Wed)
by LtWorf (subscriber, #124958)
[Link] (10 responses)
If you develop for ios using native stuff, you are not doing anything that will ever be portable, so why would you use autotools, a tool aimed at portability?
Posted Jan 31, 2024 17:51 UTC (Wed)
by Cyberax (✭ supporter ✭, #52523)
[Link] (9 responses)
Both have similar issues, especially when you want fat binaries to natively support x86_64 and arm64.
> so why would you use autotools, a tool aimed at portability?
I'm sorry, but do you understand the word "portable"? Autotools is supposed to make projects portable, so they can be used in all kinds of environments.
Yet they fail miserably for the most commonly used operating systems.
Posted Jan 31, 2024 18:56 UTC (Wed)
by LtWorf (subscriber, #124958)
[Link] (5 responses)
I understand perfectly, but I'm sure you know very well that autotools isn't magic and if you link 25 libraries that are unique to ios, your application isn't going anywhere else anyway. So why would you want to use a tool for portability while writing an intrinsically non-portable application?
Ah yes, to make "gotcha, i'm so clever" comments.
Posted Jan 31, 2024 19:27 UTC (Wed)
by Cyberax (✭ supporter ✭, #52523)
[Link] (4 responses)
No. I'm not talking about making an iOS app portable. I'm talking about simply _consuming_ third-party libraries that use autotools in an iOS application. You know, the whole reason for the existence of autotools.
Yet they fail even with this very basic functionality.
Meanwhile, using Rust with Cargo is seamless, even for more complicated scenarios.
Posted Jan 31, 2024 19:44 UTC (Wed)
by pizza (subscriber, #46)
[Link] (3 responses)
Please enlighten us how one can build an iOS application or library using anything other than XCode.
Posted Jan 31, 2024 19:49 UTC (Wed)
by Cyberax (✭ supporter ✭, #52523)
[Link] (2 responses)
You write CMakeFiles to describe your library and deps, and then generate an XCode project from it. On Windows, you can generate an MSVS project.
Posted Feb 4, 2024 14:01 UTC (Sun)
by pizza (subscriber, #46)
[Link] (1 responses)
But.. that's still using XCode/MSVS?
If the complaint is that the full autotools suite doesn't generate anything other than pure makefiles, that's a technical problem that can be rectified by someone willing to put the work into said feature. [1]
FWIW, a quick bit of googling shows several folks successfully generating iOS libraries for autotools-based projects -- It's effectively just another cross-compile target. But each (major) iOS/etc version (and sometimes hardware family on top of that) requires a different target tuple and toolchain options that are not easily discoverable, and you need to rinse and repeat multiple times to generate all of the variations that XCode needs. Which is a real PITA.
[1] Granted, it's unlikely that folks with high levels of autotools know-how are motivated to add support for probably the most FOSS-hostile platform widely deployed today. And adding Windows support requires a _lot_ more work than just changing the build system.
Posted Feb 4, 2024 14:52 UTC (Sun)
by Cyberax (✭ supporter ✭, #52523)
[Link]
Not quite. The build description remains in CMake. XCode/MSVS are used to work with the code.
> If the complaint is that the full autotools suite doesn't generate anything other than pure makefiles, that's a technical problem that can be rectified by someone willing to put the work into said feature. [1]
No. It can't be reasonably done in autotools without making them into CMake. It's possible in the same sense as all Turing-complete languages can emulate each other.
> FWIW, a quick bit of googling shows several folks successfully generating iOS libraries for autotools-based projects
Try it for real. It JustDoesntWork without tons of fiddling.
Posted Jan 31, 2024 19:13 UTC (Wed)
by pizza (subscriber, #46)
[Link] (2 responses)
You're being facetious.
Windows code isn't portable or reusable with anything else. MacOS code isn't portable [1]. iOS anything isn't portable. Ditto Android. Every one of those platforms has a bajillion bespoke quirks and [mis]features, including mutally incompatible toolchains and build environments. The only "portable" code possible is stuff that is purely computational; everything else has to have some sort of bespoke interface to the specific platform being targeted, or you target a metaplatform (eg SDL, QT, or Unreal Engine 5) that abstracts away those platform differences for you. Even then, you're still nearly always stuck with a separate "project" for each target platform's bespoke build/toolchains.
Autotools only ever covered UNIX-ish platforms [2], which at least nominally attempted to adhere to a common specification. And autotools itself didn't magically handle those platform differences for you; all it could do is detect what was or wasn't available and adjust the build (or fail) accordingly. It was always incumbent upon to the software author to actually write non-trivial platform-specific code for themselves.
In other news, Cars can't drive on water, and trains can't fly. More than once, anyway.
[1] The GUI stuff was never portable; pure cmdline stuff is in theory, but it's grown more difficult in practice with each successive release.
Posted Jan 31, 2024 19:48 UTC (Wed)
by Cyberax (✭ supporter ✭, #52523)
[Link]
No. I'm not. I'm saying that the traditional C-based infrastructure (C + autotools) is failing at the very basic level: it can't even be used on the most common OSes.
My personal most recent autohell-related nightmare was with building libtiff for iOS.
> The only "portable" code possible is stuff that is purely computational
Yet autotools fail even with that.
> The only "portable" code possible is stuff that is purely computational; everything else has to have some sort of bespoke interface to the specific platform being targeted, or you target a metaplatform (eg SDL, QT, or Unreal Engine 5) that abstracts away those platform differences for you. Even then, you're still nearly always stuck with a separate "project" for each target platform's bespoke build/toolchains.
But somehow, we can make that work with CMake. Not without issues, but it works, and I can use normal native toolchains (XCode/MSVS) with them.
Posted Feb 1, 2024 23:54 UTC (Thu)
by dankamongmen (subscriber, #35141)
[Link]
Posted Jan 31, 2024 1:09 UTC (Wed)
by khim (subscriber, #9252)
[Link] (2 responses)
Why would it come from distribution packages? They don't support Windows and don't support macOS, either. So neither my game rig nor work computer have that many distribution packages. Sure, ditribution-provided packages are important for WSL viability and use with docker image on macOS that I need to develop some software, but in the grand scheme of things I don't need that many distro-provided packages for that. And if I want only to use some software then I can, finally, use the version of LibreOffice I like and version of GIMP I like without thinking about crazy dilemma of whether it's a good idea to upgrade every single app just to get that new feature in one of them. You just have to accept that Linux on desktop have arrived — only it arrived in a form WSL. And it's now merely a tool for software development for various non-desktop platforms. That changes dynamics significantly. The only problem with that reasoning is that Linux distros today, for the majority of people are firmly in the something needed for work, which you have to use despite all the pain it causes. Distros were born in times when Internet was expensive to solve the problem which haven't existed for decade or more: how to efficiently distribute software that software maker couldn't send from their web site. Hence the name. Today they are no longer needed and just cause needless friction and pain (although if you have to develop software using GNU-based tools they are often indispensable): they impede delivery of apps from developer to your system, they tie various software packages into one huge bundle (kinda-sorta solvable if you use Gentoo, but even there it's quite painful to update one single package), they are very much a solution in a search of a problem. Seriously? Show me how can I can create a trivial “Hello world” program and we'll go from there. Assume I'm typical developer and thus pick platforms to support from this list. Which means I have to support Android, Windows, iOS, OS X before I would think about supporting Linux. And if I'm typical developer then I would, of course, use Visual Studio Code or maybe CLion, or, perhaps, even Visual Studio proper (although Visual Studio Code is more popular novadays). CMake support all the things that matter (but fails in 1% of fringe cases), autotools solve perfectly lots of fringe cases that I don't care about while ignoring 90% if not 99% of cases which people actually care about.
Posted Jan 31, 2024 10:16 UTC (Wed)
by LtWorf (subscriber, #124958)
[Link] (1 responses)
You went from using a distribution one comment above to not using one. Anyway brew and WSL are/use distributions.
Also, if you use the apple app store you are using a (non-free) distribution.
Also, I'm sure your work uses gaming rigs and osx for servers right?
You are argumenting for the sake of argumenting, knowing fully well that what you are saying makes no sense.
Users want distributions. They don't want to install your special PGP key for your special archive for your special software, of which you will proceed to lose the private key breaking automatic updates for everyone. They want the whole system to just do its thing and keep working.
> Show me how can I can create a trivial “Hello world” program
Why not a "return 0" directly if we want to make useless examples?
> autotools solve perfectly lots of fringe cases that I don't care about
if your software has the complexity of "hello world", yes I agree that you should not use autotools. But you should probably also abstain from giving advice trying to pass as an expert in portable software, when in reality you only develop trivial software for one single platform.
In the end, it seems you will argue anything and its opposite just to argue, and I have the feeling you've even forgotten what you were arguing for, so I'll stop this futile exchange.
Posted Jan 31, 2024 11:54 UTC (Wed)
by khim (subscriber, #9252)
[Link]
It's always your choice, but no, I haven't forgottten (if, maybe, you did). Let's do quick summary, shell we? And here we have bunch of half-truths that you use to try to derail the discussion While 100% truth on the surface in reality it doesn't support you cause. Yes, the majority of people only want the whole system to just do its thing and keep working… but no, they don't want to see shared libraries (or anything else) being silently upgraded behind their backs! Internet is full of tutorials which people are using to stop these updates that developers and OS makers shove down their throats. And most user never upgrade anything unless forced or unless they know what they get after update (not much these days, times when new versions were sold for money and actually had to demonstrate some kind of viability are gone) You don't need shared libraries or stable ABI to never upgrade. Here's typical story from the “what real users want”. They just want things that work. Nothing less, nothing more. Who said I only develop trivial software for one single platform? Software I develop actually is used on most platforms: Windows, MacOS, Android, ChromeOS, even Linux! It's not used on iOS, which makes me a bit atypical (most developers would care about iOS before they would think about ChromeOS), but not too much. You, on the other hand, are trying, repeatedly, to ignore with prejudice 99% of the picture (most popular OSes and most users) and only concentrate on one, very fringe, case. You say it with such an extreme conviction like just sprouting these words would make them true. But most users don/t even know what PGP key is and would definitely not try to install it even if told. They know how to click on link and select the checkbox, though. That's what they expect and that's what they get in the majority of use cases. Another truth designed the muddle the water: yes, Windows store and Apple's App Store may be classified as “distributions”, if you squint just right, but they don't need shared libraries (and they actually provide stable ABI to app developers while the majority of Linux distros still refuse to do that). True, but, most of the time, in modern world, they are glorified package managers for C and C++ packages. It's entirely not clear why some other language which, unlike C or C++, already have a standard package manager should deal with them. Sure, if someone wants/need to merge Rust packages and compiler into these distros then they can do that. It's free world and licenses permit that. But it's entirely not clear why Rust developers should spend time and money supporting something they don't need or want.
Posted Feb 2, 2024 13:01 UTC (Fri)
by mathstuf (subscriber, #69389)
[Link] (3 responses)
Supporting MSVC as a compiler[1]? C++ modules support is also something I'm interested to see how autotools will support (I implemented it in CMake; there are things that really need proper modelling that autoconf doesn't provide AFAIK).
FD: CMake developer
[1] The FAQ still has this as a TODO at least: https://www.gnu.org/software/automake/faq/autotools-faq.h...
Posted Feb 2, 2024 13:02 UTC (Fri)
by mathstuf (subscriber, #69389)
[Link] (1 responses)
Posted Feb 2, 2024 16:44 UTC (Fri)
by khim (subscriber, #9252)
[Link]
With Kati this should be possible, someone just need to intergrate it into all that automake machinery to teach it to properly rebuild ninja files when that's needed. Not sure if anyone have ever done that or not, though.
Posted Feb 2, 2024 15:52 UTC (Fri)
by Cyberax (✭ supporter ✭, #52523)
[Link]
All C++ build tools suck, but CMake at least works properly. And the backwards compatibility is great.
Posted Jan 31, 2024 17:26 UTC (Wed)
by pizza (subscriber, #46)
[Link]
I don't personally WANT that, but binary-only libraries represent very real use cases, especially in the commercial sector where sharing source code will NEVER happen.
Posted Jan 30, 2024 11:44 UTC (Tue)
by khim (subscriber, #9252)
[Link]
There are absolutely no such pretence. Rust supports binary libraries just fine. It just makes the people who produce these to pay for the ABI stability and the ability to use shared library compiled with different versions of rust compiler. It's the same deal lovers of tracing GC got: tracing GC is not outright banned by Rust, there are various crates and other things… but since that means tracing GC lovers pay actual price of having that abomination in your codebase… proposal, suddenly, becomes crazy unattractive to the majority of people. I would say that when C++ ended up with frozen ABI and all these other things that follow from that it violated it's very basic principle: you don't pay for what you don't use. Everyone pays huge price for the ABI stability — even people who don't need it. That's wrong. Now, it would probably be an interesting exercise to think about how one may introduce some kind of optional opt-in sublibrary which would exist somewhat “outside” of normal But I'm very unsure about whether all that complexity is even needed. C/C++ (and also, probably, Java) are weird outliers as far as ABI stability is concerned. Most other languages don't do that and after looking on what happened in Java world when it needed to update one measly library because there was vulnerability found in it… I would rather keep all that work entirely separate from core language. It's not that I'm against binary packages, far from it, I'm against makers of these binary packages dictating the rules. They want to ensure these would be usable in future versions of Rust, they have to pay for the development and support of that ability. P.S. One interesting example which I don't know what to think about is Swift. Swift developers have consciously decided to pay a very hefty price for the ABI stability, because Apple needed it. It made Swift an obvious Apple-only language and that helps to mitigate the damage (Apple have the ability to make developers upgrade). Not sure if Rust may adopt that model and even less sure about whether it's worth adopting it.
Posted Jan 29, 2024 21:24 UTC (Mon)
by atnot (subscriber, #124910)
[Link] (60 responses)
But it is not a breaking change, so it's not relevant to the article.
If you mean implicit stable ABI by default for everything, that's just not going to happen. There's a reason nobody is seriously proposing it: It has huge costs, there's no real arguments for it beyond "it's what c and c++ do" (even there most of the people working on the standard seem to want to break ABI) and worst of all it'd just be pointless anyway.
It's not enough for the language to have a stable ABI, the library itself needs to have a stable ABI too. Templates/generics and macros can not be linked and break ABI badly; ideomatic code uses all of those very heavily, especially in Rust. Nothing that generic code touches could ever be changed, which in Rust is effectively everything. This means that you can't just transparently use something as a shared library anyway, unless the authors have made a concerted effort to keep a specific interface ABI stable. At this point, you might as well just make the authors tag those interfaces with a special "stable ABI" flag to avoid putting this burden on the whole ecosystem. And perhaps it could be used to make the linter yell at you if you make a mistake too, like it already does for C.
And thus, we have arrived back at crabi :)
Posted Jan 30, 2024 8:35 UTC (Tue)
by gspr (guest, #91542)
[Link] (58 responses)
I agree about the huge costs (which can be mitigated by, as others have suggested, for example only promising ABI stability for an edition, or perhaps even just for a range of rustc versions), but there are definitely upsides, too. For example, it's a major obstacle to Rust's profileration in traditional Linux distros. It's also an important piece of functionality for software modularity – there are situations where one *cannot* simply rebuild everything, and swapping out a shared library is highly desirable.
> It's not enough for the language to have a stable ABI, the library itself needs to have a stable ABI too. Templates/generics and macros can not be linked and break ABI badly; ideomatic code uses all of those very heavily, especially in Rust.
True. But one can imagine a halfway solution: simply build the shared library with an explicit list of monomorphized generic functions/types and expanded macros. In a "closed world" system where you know every user of the library (for example a traditional distro), this seems feasible.
Posted Jan 30, 2024 9:34 UTC (Tue)
by matthias (subscriber, #94967)
[Link] (1 responses)
Since when is a distro a closed world? There is constant addition of new packages. Do you want to recompile all the libraries whenever a new package is added? Or a new version of a package?
And quite likely, the generics are instantiated with types that are defined by the consumer of the library. Otherwise, there is not much value in having generics on the public interface anyway. Why should this code that is only relevant to the consumer be in the shared library? It would be infeasible anyway, as you would need the code of all consumers to actually build the library.
Posted Jan 30, 2024 9:59 UTC (Tue)
by gspr (guest, #91542)
[Link]
Ant any given moment, it is. It only needs to consider its own set of packages. Yes, that set changes over time, but the point is just that the Rust libraries can *serve the rest of the distro* using explicitly monomorphized versions of their generic types/functions. (Locally installed consumers, of course, would need to revert to the current status quo unless they happen to need variants of the generics that are already monomorphized in the distro.)
> Do you want to recompile all the libraries whenever a new package is added?
No. I believe that with what I suggest, you'd only need to recompile libraries for which said package requires new concrete variants of generic types/functions, or new macro expansions. That's certainly better than the "rebuild everything" world that is today.
> And quite likely, the generics are instantiated with types that are defined by the consumer of the library. Otherwise, there is not much value in having generics on the public interface anyway. Why should this code that is only relevant to the consumer be in the shared library?
Because it's library code. And hopefully there'd be significant overlap between consumers.
> It would be infeasible anyway, as you would need the code of all consumers to actually build the library.
This is exactly why it's important that distros are closed systems. You *can* extract all that information from all consumers, because you have an explicit set of consumers.
Posted Jan 30, 2024 10:08 UTC (Tue)
by atnot (subscriber, #124910)
[Link] (52 responses)
This is true, but it's true for basically every recent compiled language, e.g. go and as mentioned template heavy C++ (and even C with the popularity of header only libraries). If a distro choses to insist on a system that only really works for 80s C code that's fine. But it's not really a cost that the rest of the ecosystem should pay. And as I sad, it just fundamentally doesn't work anyway, so the whole discussion is pointless.
> there are situations where one *cannot* simply rebuild everything, and swapping out a shared library is highly desirable
I disagree with the sentiment that it cannot be done, distros with sensible automated processes (i.e. not debian) can and do do this regularly. However do I agree that it can sometimes be useful to be able to split software up into modular files. There's usually really only a handful of things one might want to urgently swap out after the fact: The TLS library, media codecs if they're not written in a memory safe language, some core system stuff, that's basically it. Those can be pretty cleanly cut off with something like crabi. Once again there is no need to put this burden on the whole ecosystem.
> True. But one can imagine a halfway solution: simply build the shared library with an explicit list of monomorphized generic functions/types and expanded macros. In a "closed world" system where you know every user of the library (for example a traditional distro), this seems feasible.
Aside from this being an absolute pain to implement and maintain, the restrictions of this would just make it pretty pointless. It means that none of the projects may ever change what generics they use, which at least in Rust, is nigh impossible when making any significant change. For reference, here's some things that instantiate generics in rust: iteration, all type conversions and arithmetic operations except primitives, any error handling. Want to quickly add a patch with an error case you missed? Good luck.
In an environment that constrained, you might as well just compile all of your stuff together at once with the same compiler, you don't need a stable ABI for that.
Addendum:
Posted Jan 30, 2024 11:23 UTC (Tue)
by gspr (guest, #91542)
[Link] (20 responses)
I agree.
> If a distro choses to insist on a system that only really works for 80s C code that's fine. But it's not really a cost that the rest of the ecosystem should pay. And as I sad, it just fundamentally doesn't work anyway, so the whole discussion is pointless.
You could imagine doing the same for heavily templated header-only C++ code: stamp out all the concrete instances you need. Then, if only the ABI itself were stable, you surely could have shared libraries as far as the distro itself goes, no?
> I disagree with the sentiment that it cannot be done, distros with sensible automated processes (i.e. not debian) can and do do this regularly.
I did not mean distros. I agree that Debian's tooling should be improved to make tasks like this easy, even if I still wish for shared libraries and ABI stability. I meant large projects where certain components may have to be rebuilt so carefully, and in such a timeconsuming and manual way, that rebuild-it-all cannot fly. Distros should indeed be able to do this.
> There's usually really only a handful of things one might want to urgently swap out after the fact: The TLS library, media codecs if they're not written in a memory safe language, some core system stuff, that's basically it. Those can be pretty cleanly cut off with something like crabi. Once again there is no need to put this burden on the whole ecosystem.
I'd argue that there's value in not special-casing such a relatively wide set of things.
> Aside from this being an absolute pain to implement and maintain
On the contrary, it strikes me as something that would be even easier to implement than the otherwise necessary (detection of) auto-rebuilds that you suggest. (Of course, Rust currently lacks support for doing monomorphization on request, but that's a different matter.)
> It means that none of the projects may ever change what generics they use, which at least in Rust, is nigh impossible when making any significant change.
I don't understand why this would be the case. Can you elaborate?
> For reference, here's some things that instantiate generics in rust: iteration, all type conversions and arithmetic operations except primitives, any error handling. Want to quickly add a patch with an error case you missed? Good luck.
Adding an error case to the public interface of any library does not seem to me to be the kind of update that should be made to a classical Linux distro package post release at all. Moreover, it already seems to be the kind of change that would require a major version bump per Rust's *current* semver guidelines! Today!
> In an environment that constrained, you might as well just compile all of your stuff together at once with the same compiler, you don't need a stable ABI for that.
That's essentially the status quo. Which causes you to miss out on the ability to swap out components seamlessly.
> Most sizeable rust libraries are split up into dozens of crates for various technical and semi-technical reasons like the tooling making it easy
That something is easy to do isn't really a compelling reason to do it.
> and speeding up compile times.
That's just because crates are compiled in parallel. It's an arbitrary distinction. That granularity could be made finer (more things in parallel, fewer cross-thing optimizations) or coarser (fewer things in parallel, more cross-thing optimizations) independently of the size of crates. (Alternatively: independently of what unit of things one considers a library – see below).
> It wouldn't be unusual for a simple single purpose dependency to have five crates: "crate", "crate-sys" for C bindings, "crate-macros" for proc macros, "crate-util" for various extras and "thing-traits" for interoperability types with the rest of the ecosystem. Packaging and dynamically linking all of these separately would be utterly ridiculous, stable ABI or not.
Perhaps. But there's also nothing preventing a libcrate library that happens to physically contain code from all five crates.
Posted Jan 30, 2024 14:24 UTC (Tue)
by farnz (subscriber, #17727)
[Link] (18 responses)
It's also worth noting, in this context, that dynamic linking with a stable ABI for C code the way we do it on Linux is a historical accident, and not something that was carefully planned with a view to programming languages other than FORTRAN and C
Sun, in the late 1980s, chose to develop their dynamic linking technology (which was later adopted into ELF) as a way of simply delaying the final link stage. Thus, the decision was made that only things that the SunOS implementation of ld handled - basically just symbol lookup and nothing else - would be part of the dynamic linker's remit. Everything else would be exactly the same as if you were building a static binary using the traditional SunOS toolchain, and thus you did not need to consider the ABI of your shared library; if you had a stable C (or FORTRAN) API, the SunOS 4.0 dynamic linker would automatically give you a shared library with a stable ABI, and any instability in the ABI would be traceable to a change in the API. However, you must then ensure that the API is completely stable from the point of view of ld - any changes, no matter how minor, immediately break the stability of the DLL.
This contrasts to the approach other systems (notably Windows) took; in the alternative approach, you have to explicitly label which parts of your API are part of your stable ABI for your dynamic library, and instability in your ABI is a consequence of you deciding to change the stable ABI. In this approach, you can supply statically-linked code around your ABI that supports API changes, but the stable ABI must stay stable.
We later adopted symbol versioning, and symbol visibility, so that library authors could provide a stable ABI under the ELF rules, but we kept the SunOS thing of "your DLL ABI is deduced from the objects you present to the final link stage" rather than switching to the Windows thing of "your DLL ABI is defined based on the things that you expect to treat as stable". If we'd switched to the latter at any point (thus had a requirement for a curated "module definition" to define a shared object, instead of deducing what should be exposed based on the C source code), then we would be headed to a very different place, since we'd be expecting languages taking part in dynamic linking (including C) to define what parts of their API are stable ABIs, and we would not have the situation we have today where it's possible to dynamically link against parts of a C API that aren't stable, while not possible to dynamically link against parts of a stable C++ API that happen to be inexpressible in C after the preprocessor has run.
Posted Jan 30, 2024 15:22 UTC (Tue)
by smcv (subscriber, #53363)
[Link] (6 responses)
It's possible to do this in a way that works with GNU or clang ELF toolchains: for example, GLib and GTK do this. Briefly, you compile and link with -fvisibility=hidden on ELF platforms, and then decorate each public/stable symbol declaration with a macro that expands to __attribute__((visibility("default"))) on ELF platforms. In portable code, it's convenient for the same macro to expand to __declspec(dllexport) or __declspec(dllimport) (as appropriate) on Windows.
I agree that this is much less obvious than it would ideally be, with a lot of things that the library maintainer is expected to "just know".
Posted Jan 30, 2024 16:12 UTC (Tue)
by farnz (subscriber, #17727)
[Link] (5 responses)
It's possible, but it's not the default, and it's a lot of work for the library developers. Most of the stuff we dynamically link is stuff like zlib, where we rely on the fact that ELF dynamic linking and ELF static linking work in almost the same way, just at different times.
Your brief guide misses the other thing, which is that you need symbol versioning as well - yet another bit of build complexity to add versions to symbols, and to deliberately handle cases where you need to change your stable ABI without breaking old users. If we'd gone for deliberate module definitions (as Windows used to require, but I believe has moved away from to closer match the way Unix systems handle dynamic linking), then this would be required if you wanted to be dynamically linked at all.
Posted Jan 30, 2024 17:26 UTC (Tue)
by josh (subscriber, #17465)
[Link] (3 responses)
And that turns out to be a monumental pain for anyone who wants to use a different zlib. Symbol conflicts between, for instance, zlib and zlib-ng-compat are painful.
The entire concept of symbol interposition and a global symbol namespace are *terrible*, and it's unfortunate that we all pay the cost for them just because people like the ability to override symbols via LD_PRELOAD.
Posted Jan 30, 2024 18:54 UTC (Tue)
by farnz (subscriber, #17727)
[Link] (2 responses)
Indeed.
If you look at this historically, the dynamic linking model we mostly use is from SunOS 4.0 in 1998, where all symbols not explicitly hidden from the dynamic linker are linked at run time, not at compile time, and the purpose of the dynamic linker is to defer symbol resolution from compile time to run time. It was not meant as a stable ABI mechanism; rather, the assumption was that you'd only supply dynamic libraries where you already had a stable ABI for your static libraries. Later, in the mid 1990s, Solaris 2.5 added symbol versioning, since it was clear that even if you had a stable ABI for your libraries, you'd run into cases where people depended on the implementation, not the defined behaviour, and you'd need to give people who thought they'd linked against the old version the old behaviour, not the new behaviour.
However, Linux distros have turned everything into dynamic libraries, without considering whether or not the library ABI is meant to be stable; by and large, this has worked out because people have done huge amounts of work to make it work. And a lot of upstreams (take Chromium for an example) have handled this by copying libraries into their source trees and patching them (since they don't have a stable ABI anyway), which now needs more work in distributions to handle de-vendoring these libraries and ensuring that the patch is either upstream, or not needed.
Posted Jan 31, 2024 10:06 UTC (Wed)
by farnz (subscriber, #17727)
[Link] (1 responses)
And I notice my typo - SunOS 4.0 is not 1998, it's 1988 - ten years earlier than I said.
Posted Jan 31, 2024 16:14 UTC (Wed)
by tialaramex (subscriber, #21167)
[Link]
Posted Jan 30, 2024 22:47 UTC (Tue)
by Cyberax (✭ supporter ✭, #52523)
[Link]
Windows now supports the "throw everything over the wall" approach, but everything native to Windows uses either explicit exports. Or COM objects that are basically completely handled in runtime.
Posted Jan 31, 2024 15:49 UTC (Wed)
by paulj (subscriber, #341)
[Link] (10 responses)
<<we kept the SunOS thing of "your DLL ABI is deduced from the objects you present to the final link stage">>
Is that really the case? We have mapfiles in which you specify your interface(s). Along with versions, with composability of these versioned interfaces though inheritance. Sun and Glibc have been doing so since the 90s. And have been doing a good job. We havn't had to suffer libC "upgrade everything on your computer" flag days since Glibc... 2.0 (?) I think - which IVR was the first glibc to really get shipped by distros (there might have been a bleeding edge release of RHL with a pre-release Glibc (1.99?) which then had to suffer a flag day upgrade to 2+ - but it was clearly marked as bleeding edge; I avoided that release anyway and went from Linux libc5 to glibc 2).
[Sun did an even better job with Solaris. Particularly on the process side, ensuring that libraries shipped with Solaris were required to specify their interface stability - both API and ABI. At a minimum, the library would ship with a man page detailing the interfaces had no stability. For anything a customer was to be allowed to have some reliance on, there were other levels of stability - with increased guarantees of stability, came increased requirements to document the interfaces in manual pages and in map files, along with increased levels of reviews to changes internally within Sun.]
Anyway, linker map files with versioned interface descriptions work _really_ well for managing C library compatibility.
For some strange reason many libraries in the open-source world continued to rely on (largely unworkable) SONAME versioning. Indeed, many don't even bother with that.
That's a _social_ problem though, not really a technical problem in the Unix C world. Cause the technical tools were there since the 90s. Sun and Glibc also solved the process and social sides, on top of the tooling.
Posted Jan 31, 2024 15:52 UTC (Wed)
by paulj (subscriber, #341)
[Link] (6 responses)
And end-users, the wider world, we all suffer for it.
Posted Jan 31, 2024 16:20 UTC (Wed)
by farnz (subscriber, #17727)
[Link] (5 responses)
The social problem is worse than that - you have upstreams working in the "wild west" style, and distros quietly doing the work to keep stable interfaces downstream. That work done by distros is then assumed to be an inherent part of the C way of working, and not serious effort by skilled people, and thus languages where no-one is doing that work are assumed to be doing it wrong, whereas in fact all they're doing is exposing the fact that somebody needs to do this work if we're to have stable interfaces.
Posted Jan 31, 2024 16:40 UTC (Wed)
by paulj (subscriber, #341)
[Link] (4 responses)
I think there was actually a moment in tech, in the mid-90s, where a bunch of engineers /did/ think about stability. And they /did/ bring in tools and processes to try tame some of the dependency and stability issues that had been seen. We got tools to at least define C interfaces and manage their stability - even if the default wasn't ideal, as you say (also, unfortunately GNU libtool went with soname versions as the recommended approach rather than symbol versioning - and soname versions just.. doesn't work - I fear this damaged the FOSS side). We got social processes to manage stability, both in some tech companies and in a (v small) number of FOSS projects (Glibc, on top of symbol versioning; Linux, of userspace API and ABI). You could argue Linux distros and packaging tools also played a significant part in trying to manage stability and tame the hell of dependencies without such management.
Sadly, we seem to have lost the drive - collectively - to manage stability.
We see many projects just ignoring tooling and packaging approaches to managing stability, and just... vendoring the fsck out of everything and giving users "wget | bash" scripts. And this has become very normalised. And the more projects do this, the more unstable everything becomes and hence the more other projects do this, because they can't rely on stability of stuff either and have to vendor.
And it's not just FOSS, this happens internally in proprietary corps too. With internal projects sucking in external and internal repos into big blobs. Even checking in .sos into build directories for various platforms and arches cause they can't figure out anything more reasonable. Cause nobody has the time to care about managing stability and dependencies, and the resulting industry wide mess means it's too complicated to even start to figure it out.
But wait, I know the answer, let's ship a script that builds container images that contain our build, so it can build...
It's really awful, and it seems to be getting worse.
Posted Jan 31, 2024 17:27 UTC (Wed)
by farnz (subscriber, #17727)
[Link] (3 responses)
The only part of it that I see as a "C problem" is the idea that C inherently has a stable ABI for dynamic linking, while other languages don't. That's a false statement - it's a historical coincidence that as engineers at Sun build what became ELF dynamic linking, they built something that assumed that if you hadn't put the effort in to first limit symbol exposure (SunOS 4.0) and then version your stable ABI (Solaris 2.5), the "right" thing to do was to expose everything as-if it's a stable ABI, rather than hide everything by default and require you to explicitly expose a stable ABI to dynamic linking.
With engineering discipline, you can work well in the Solaris 2.5 model of a version script indicating exactly what symbols are part of your stable ABI, and what symbols are internal, combined with a static library and source code that provides a "useful" stable API on top of the stable ABI (but where the ABI of the static library and matching source is itself unstable). But we don't do that for any language; Rust, Go, C++ et al merely expose that our "stable ABI" trickery is not engineered in, but something that only exists for C because there's a lot of invisible work done to hide the fact that the libraries don't have a stable ABI by default.
Posted Jan 31, 2024 17:44 UTC (Wed)
by paulj (subscriber, #341)
[Link] (2 responses)
It was a little bit of sound engineering work, and quite manageable.
Really, where we are today, it's largely a cultural thing - we just don't want stability. Or more precisely, we want stability from everyone else's software, but we don't want to held to that ourselves. We - as an industry - are largely a bunch of degens who just want to slap code around, and not be held responsible much later. Either because we work in some big-walled-garden tech corp, and all that matters is throwing enough code around to get past the next performance review; or because we work somewhere where managers are putting pressure on you to get that next hacky-bug-fix or half-arsed-feature-update shipped to the customers, so that you can move onto the next hacky-bug-fix or half-arsed-feature-update.
Sorry... I've become very cynical. But then, I've been watching some of the videos of testimony from the UK Post Office scandal, particularly the software engineer describing the code issues and practices and.. I really am not being cynical, we really are just a completely degenerate industry.
Posted Feb 1, 2024 10:59 UTC (Thu)
by farnz (subscriber, #17727)
[Link]
See also a subset of the fuss around the EU's Cyber Resilience Act; not the people talking to MEPs and national Commissioners about getting the details right (e.g. ensuring that I can't be held liable if you use my software for a purpose I did not advertise it as suitable for), but the people arguing that any form of liability for defects in software will destroy the software industry completely. In effect, they're saying that the software industry (including embedded) cannot exist if software vendors can be held liable for faults in the software they sell.
Posted Feb 1, 2024 16:09 UTC (Thu)
by Hattifnattar (subscriber, #93737)
[Link]
Early on, programming was an elite undertaking, with people practicing it having a mindset closer to scientists. It was elite job, commanded elite compensation (not necessary monetary, esteem too). The "industry" also had been largely driven by people with more-or-less engineer or scientist mindset.
Now software is a real and huge industry, programmer work is commoditized, the whole thing is more and more run by people with "financist" and/or "bureaucrat" mindset, "buccaneer" in the best case.
There are still "islands of quality" in this mess, mostly dealing with foundations. But they cannot completely isolate themselves from the overall dynamics. However, understanding it can can help to draw some boundaries and do better in specific areas/projects etc.
Posted Jan 31, 2024 16:22 UTC (Wed)
by farnz (subscriber, #17727)
[Link]
Yes, it is - like Solaris 2.5, we kept the thing where if you don't supply a version script specifying your interface, we assume that you intended for every single linker-visible symbol in your library to be part of your static ABI.
If the default were the other way round, where libraries that don't carefully specify their exported ABI have no exported ABI at all, it'd be abundantly obvious to everyone that most C libraries don't actually have a stable ABI for dynamic linking, either (glibc an important and notable exception here, and there are others like glib that would specify their exported ABI in this world).
And then the distro thing of "un-vendoring" sources and exporting a suitable "stable ABI" from libraries would be highly visible work, not (as it is today) invisible work where distro packagers put in a lot of effort to make useful stable ABIs for C libraries whose upstreams don't even care about having a stable API, let alone a stable ABI.
As it is, though, we have a set of upstreams who don't care, but where distros can do the work so that as long as you don't look too closely (and especially if you don't look at the work done by distro packagers), it looks a bit like all C libraries have a stable ABI with no effort. Following from this, any language where this work can't be done quietly by the distros, but will need invasive changes to upstream "looks" bad compared to C, because it's assumed that the distros are "just" building binaries, when there's a lot more social work involved around keeping stable interfaces in place.
Posted Jan 31, 2024 16:48 UTC (Wed)
by Wol (subscriber, #4433)
[Link] (1 responses)
Linux didn't UPGRADE to glibc2, it CHANGED to glibc2.
Before that we had libc5, which was a completely different libc. I bang on about WordPerfect, but the native linux WP was a libc5 beast. So I gather you can still force it to run, but it's a lot of work.
Cheers,
Posted Jan 31, 2024 17:10 UTC (Wed)
by paulj (subscriber, #341)
[Link]
Libc.so.5 was a "Upgrade everything on your computer" type libc though. Any major updates to it would have required updating most of your Linux box. That this didn't happen was only because Linux libc lasted only a relative short period of time, as a stop-gap fork of an earlier Glibc 1.
That I and others "changed" from Linux libc to Glibc 2.0, to gain symbol versioned libc - which then meant "upgrade your entire distro" was largely avoided (there was some foible in compatibility with an ancillary library around Glibc 2.2 IVR) - is kind of irrelevant. Cause it would equally have required a complete distro upgrade to have "upgraded" Linux libc to a symbol versioned one. And anyway, Linux libc /was/ GNU libc.
Without symbol versioning, as in the Linux libc days, we would have had to have suffered "flag day" upgrades, updating your entire OS for any ABI fixes to core (heavily depended upon) libraries.
Posted Jan 30, 2024 15:58 UTC (Tue)
by atnot (subscriber, #124910)
[Link]
Both of these are sort of true from the public library interface side, with the exception of the small edge case that it's backwards compatible to add new variants to enums tagged "non_exhaustive", as errors usually are.
But what I was really trying to get at is that your suggestion of working around the generics issue by pre-monomorphizing them creates not just stabilities on the public interfaces, but the private interfaces and internal usage too. The point is, it's very hard to change anything about a rust program without instantiating new generics somewhere, which would require recompiling the library anyway.
I also just noticed another reason it wouldn't work:
And again, all of this can just be avoided by just making ABI compatibility something you can opt into at the interface level so the compiler can yell at you if you expose your internals across the API boundary like this.
Posted Jan 30, 2024 12:02 UTC (Tue)
by bluca (subscriber, #118303)
[Link] (30 responses)
It's a system that works perfectly fine for everything _but_ 3 ecosystems: Rust, Go and NodeJS. Entirely coincidentally I'm sure, but all of them are essentially tools designed to be extremely convenient for an individual application developer, at the detriment of everybody else, and for massive corporations with armies of engineers that ship a handful of applications and can deal with the constant high churn. They are really not designed for, mature enough and appropriate for other use cases.
So why should cash-strapped volunteers in distributions pay for that, instead of the handful of massive corporations that are behind those ecosystems? Who's going to volunteer their money and time to upgrade and maintain those "systems that only really work for 80s C code"?
In Debian, I'm pretty sure we'd need to redo the entire build and release infrastructure from zero, and increase the relevant teams sizes by several orders of magnitude to deal with the increased maintenance costs. Who's going to provide the tens of millions it will cost in engineering hours to do so?
The real answer is, of course, nobody. The only realistic choice I see is that we simply expunge all Rust and Go from Debian, and make it a problem for the upstream ecosystems on how to distribute them outside of the handful of megacorps where they are used right now, until there is sensible support for dynamic linking by default and the standard library stops breaking ABI on every build.
Posted Jan 30, 2024 14:20 UTC (Tue)
by farnz (subscriber, #17727)
[Link] (21 responses)
It also doesn't work well for C++ (similar issues to Rust and Go), Perl, Python or Ruby (similar issues to NodeJS). The only ecosystems it works "perfectly fine" for are the C and Fortran ecosystems, and it's not coincidental that dynamic linking as implemented in ELF directly descends from SunOS 4.0's work on dynamic linking for Sun's C and FORTRAN toolchains.
Posted Jan 30, 2024 14:31 UTC (Tue)
by bluca (subscriber, #118303)
[Link] (20 responses)
Posted Jan 30, 2024 15:31 UTC (Tue)
by farnz (subscriber, #17727)
[Link] (13 responses)
Only because Perl, Python, and Ruby are recompiled automatically at runtime - if we insisted on compiling them fully on builders, we'd have the same problem. We still have the NodeJS problem, where there's a huge number of packages, all inter-dependent, but that's manageable because the communities for Python, Perl and Ruby are happy to maintain a huge number of packages for them, where the NodeJS community mostly bypasses distros (although note that the Python community is moving this way, too).
Posted Jan 30, 2024 16:10 UTC (Tue)
by bluca (subscriber, #118303)
[Link] (12 responses)
Posted Jan 30, 2024 16:18 UTC (Tue)
by farnz (subscriber, #17727)
[Link]
That's not my experience of maintaining a binary module for Python in a distro - I have to "recompile the universe" (in as much as I have to recompile the universe for Go or Rust code) every time something changes in the distro. It works, because (like a lot of packagers), I'm putting in the effort to make sure it's not hard to recompile the world when a dependency changes, and I'm doing the work to get those recompiles to happen, but it's not "free" at all.
Effectively, what you're saying is that because people put a lot of work into Python and Perl, but not NodeJS packages for distros, Python and Perl are fine, but NodeJS is a problem because no-one's willing to put the work in to maintain distro packages when npm works just fine for them.
Also worth noting that Fedora has had someone (mostly Fabio) do the work to make it easy to package Rust in Fedora. It's thus fairly trivial to maintain a Rust package in Fedora, because Fabio and a few others have made it easy.
Posted Jan 31, 2024 9:00 UTC (Wed)
by pbonzini (subscriber, #60935)
[Link] (10 responses)
You can do the same in Rust. Let the crates export a stable C ABI that can be used for dynamic linking. That's how librsvg works and in fact it's also how it's often done for C++, see for example libclang (though that didn't prevent them from breaking ABI by mistake at least once).
Posted Jan 31, 2024 11:39 UTC (Wed)
by bluca (subscriber, #118303)
[Link] (9 responses)
Posted Jan 31, 2024 12:16 UTC (Wed)
by pbonzini (subscriber, #60935)
[Link] (8 responses)
Posted Jan 31, 2024 13:22 UTC (Wed)
by bluca (subscriber, #118303)
[Link] (7 responses)
Posted Jan 31, 2024 14:25 UTC (Wed)
by mb (subscriber, #50428)
[Link] (6 responses)
Posted Jan 31, 2024 15:46 UTC (Wed)
by bluca (subscriber, #118303)
[Link] (5 responses)
Posted Jan 31, 2024 16:01 UTC (Wed)
by pbonzini (subscriber, #60935)
[Link] (4 responses)
Posted Jan 31, 2024 19:01 UTC (Wed)
by bluca (subscriber, #118303)
[Link] (3 responses)
Posted Feb 1, 2024 8:38 UTC (Thu)
by pbonzini (subscriber, #60935)
[Link] (2 responses)
Posted Feb 1, 2024 10:07 UTC (Thu)
by farnz (subscriber, #17727)
[Link]
Not just C++; even C has this problem to a small degree. The ELF ABIs don't cover the full C language; if I change a significant preprocessor definition, or a const that my library never takes the address of, the resulting .so will not change, and yet I can change my ABI by doing so.
We just ignore this for C, since distros do the work of making sure that C developers don't destabilize their own ABIs this way, but it's still an issue, even in C.
Posted Feb 1, 2024 14:33 UTC (Thu)
by bluca (subscriber, #118303)
[Link]
Posted Jan 30, 2024 22:49 UTC (Tue)
by Cyberax (✭ supporter ✭, #52523)
[Link] (4 responses)
Nope. Nope. Nope. Try installing Tensorflow or PyTorch in Python. Or the recent-ish Ruby On Rails using only distro-provided packages.
Posted Jan 31, 2024 6:20 UTC (Wed)
by zdzichu (subscriber, #17118)
[Link] (3 responses)
Posted Jan 31, 2024 6:23 UTC (Wed)
by Cyberax (✭ supporter ✭, #52523)
[Link] (2 responses)
And it doesn't include everything needed. Which will force you to install PyTorch manually.
See comments like: "this requires CUDA and is likely not package-able for Fedora. we'll have to figure out how to build pytorch without it."
Posted Jan 31, 2024 7:13 UTC (Wed)
by aragilar (subscriber, #122569)
[Link] (1 responses)
I suspect though because many of the machine learning libraries produce different results with different versions, users will want to freeze exact versions of everything, and only move when they tested with their particular code/dataset pair a new set of versions (which likely means freezing the versions forever because of a lack of time/funds), which isn't exactly amenable to using a distro-provided package.
Posted Jan 31, 2024 10:11 UTC (Wed)
by farnz (subscriber, #17727)
[Link]
The ROCm variation of PyTorch is also unpackaged, even though it's fully open-source down to the kernel modules, and thus licensing is not an issue.
Posted Feb 1, 2024 16:19 UTC (Thu)
by Hattifnattar (subscriber, #93737)
[Link]
Maybe I misunderstand what you are talking about. But in my experience, pretty much everybody who works on serious Perl applications rely on CPAN for getting modules, not their Linux distribution. I know that distributions try to package Perl modules, and I assume somebody uses it, but in real life I've seen only quick-and-dirty scripts and one-liners utilize that. Of course it's only one guy experience...
Posted Jan 30, 2024 16:54 UTC (Tue)
by atnot (subscriber, #124910)
[Link] (7 responses)
I mean, you're correct there. I'll put it this way: There is one (1) notable linux distribution where packages are still added to the archives by semi-random people just uploading them to an FTP site from their personal computers as and when they please. I get that the Debian governance processes makes this sort of thing hard to change, but it's not rocket science. Distros with a fraction of Debian's resources can and do rebuild most or all of their repository in one or zero clicks and make sweeping changes across all packages regularly. None of them make you write a patch for a linter and wait a year. And it's not the package count, as far as I can tell, Debian has the worst ratio of active members to resulting source packages of any distribution.
I really do appreciate the work that Debian does, I wouldn't care as much about this if I didn't. But yes, maybe entirely new build and release tooling should be something to be look into.
Posted Jan 30, 2024 17:08 UTC (Tue)
by bluca (subscriber, #118303)
[Link] (6 responses)
And which one distribution would that be? Because for sure it's not Debian, where all binary packages in a release must have been built on the distro build infrastructure since at least a few releases.
> But yes, maybe entirely new build and release tooling should be something to be look into.
Cool, when are you getting started and by which date can we expect to see the fully working result? Or alternatively, when are you going to wire the tens of millions required to pay for such work to happen?
Posted Jan 30, 2024 19:04 UTC (Tue)
by atnot (subscriber, #124910)
[Link] (5 responses)
It's a good step, what about source packages.
> Cool, when are you getting started and by which date can we expect to see the fully working result? Or alternatively, when are you going to wire the tens of millions required to pay for such work to happen?
Look, there's plenty of people inside debian who have tried to do this. And the tens of millions figure doesn't get any less ridiculous the more you say it. There's single-person distros who manage this stuff. There's small teams inside debian that manage this (e.g. the go packaging team). The obstacle has always been political, down from the hermit maintainers that get upset and boycott a change if you meddle with their fiefdom by asking them to use a new version of a packaging tool, to the DPLs that run on a platform of not changing anything. Perhaps using your hypothetical tens of millions of dollars to bribe every developer with $10k would help that though.
Posted Jan 31, 2024 11:59 UTC (Wed)
by bluca (subscriber, #118303)
[Link] (4 responses)
What about them?
> Look, there's plenty of people inside debian who have tried to do this. And the tens of millions figure doesn't get any less ridiculous the more you say it. There's single-person distros who manage this stuff. There's small teams inside debian that manage this (e.g. the go packaging team). The obstacle has always been political, down from the hermit maintainers that get upset and boycott a change if you meddle with their fiefdom by asking them to use a new version of a packaging tool, to the DPLs that run on a platform of not changing anything. Perhaps using your hypothetical tens of millions of dollars to bribe every developer with $10k would help that though.
Yes, there are plenty of people who have tried and are trying, and they are all ultimately failing. Not their fault of course, they are fighting an unwinnable, uphill struggle against an hostile ecosystem that couldn't possibly care less about them. So yes, to actually make this viable it would cost tens of millions, because it would need a few dozens of FTE engineers to work on it forever, and labor costs money in this universe.
Posted Jan 31, 2024 14:11 UTC (Wed)
by joib (subscriber, #8541)
[Link] (3 responses)
So perhaps the issue here is that it's the traditional Linux distros that need to adapt to how language ecosystems and software ecosystems work outside the 1980'ies C world? You can tilt at the windmills and scream "CADT!!!" all you want, but the rest of the world is not going to drop whatever they're doing and go back to the good 'ole days of plain C and POSIX sh. There's a lot of crap ideas out there, but all ideas that are different than the 1980'ies C view of the world are not automatically bad.
Posted Jan 31, 2024 14:18 UTC (Wed)
by pizza (subscriber, #46)
[Link] (2 responses)
....Yeah, how dare folks want to be able to handle their traditional use cases. The same use cases that all of these other language ecosystems stand on top of.
I swear, this really does come off as a "I don't care about the plights of farmers; I get my food from a supermarket!" dismissal.
Posted Jan 31, 2024 14:33 UTC (Wed)
by joib (subscriber, #8541)
[Link]
Just saying that trying to bludgeon the post-2000(-ish) software development world into the shape of the C & sh world is unlikely to be satisfactory to anyone. As there are orders of magnitude more non-distro developers out there, and the distros have little to no leverage over them, it seems this is a fight they're destined to lose.
Posted Feb 3, 2024 1:43 UTC (Sat)
by jschrod (subscriber, #1646)
[Link]
This should go into LWN's quote section.
Posted Jan 30, 2024 15:11 UTC (Tue)
by Wol (subscriber, #4433)
[Link] (2 responses)
Dunno how feasible it is, (seeing as I don't know how rust compiles and links) but how does rust define the linkage between rust and C? If a binary crate can store the equivalent of a .h declarations file in it, then you can define the rust interface, what "variations on a theme" are exported, and the assorted mappings. Then when rustc imports a pre-compiled binary crate into a program, it knows what routines are available and how to call them.
And if you want a dynamic linker, it also has the information, at runtime, to link this stuff, along with the ability to panic or warn that various functions it expects aren't available ... along with telling any other language how to call all this stuff ...
(And because it's all declared in the binary header, you can move on and obsolete all this stuff if you so require :-)
Cheers,
Posted Jan 30, 2024 15:16 UTC (Tue)
by Wol (subscriber, #4433)
[Link] (1 responses)
So rust should simply declare the stable ABI as part of a header in the crate. Then the linker can check that the crate has the expected ABI - could be as simple as an MD5 over declaration file.
Cheers,
Posted Feb 2, 2024 17:48 UTC (Fri)
by mathstuf (subscriber, #69389)
[Link]
Posted Jan 30, 2024 11:53 UTC (Tue)
by bluca (subscriber, #118303)
[Link]
There are huge costs _not_ to have that too, but of course they are paid by other people, not by the rust core developers. What you are saying is, somebody else has to pay for it, so we don't care.
Posted Feb 5, 2024 10:25 UTC (Mon)
by ras (subscriber, #33059)
[Link]
Right now, I'd settle for a stable set of I/O traits. Not implementations or ABI's, just traits. As it stands, you have to use one I/O library for normal programs, or if you pick an async implementation you've got to use it's I/O library, ditto with green threads. It's enough to make the poor schmuck trying to write some library compatible with everything pull his hair it. Right now, if your library has to do I/O it's damned near impossible to work with everything.
The software world is built on re-use. This multitude of incompatible colours is the opposite of reuse. It's a fragmentation bomb for an ecosystem.
Posted Jan 29, 2024 20:09 UTC (Mon)
by tialaramex (subscriber, #21167)
[Link] (13 responses)
I think even after you discount the people who'll go "I didn't want a function pointer anyway, WTF?" because they've made a typo - the remaining people mostly don't realise what a bad idea this actually is and abolishing the 'as' cast helps clarify that.
A pointer has three components, an address (which is an integer), an address space (Rust doesn't care about this, good luck), and a validity context (nebulous but in hardware sometimes represented as some sort of address range)
The 'as' cast discards both the address space and context, leaving you just that integer. If what you needed was literally an integer to distinguish function A from function B, which you are *very certain* are literally different machine code (because if they are not you lose) and they live in the same address space (Rust doesn't guarantee that) and your integer is big enough (this either) then this will work. Otherwise it's just some integer.
If you intend to reconstitute the integer into a pointer, you need the address space and context or you're entirely relying on the compiler to happen to decide what you're doing is OK. Maybe it is OK. Maybe it isn't, we don't know. First Bad news, Rust provides *no way* to specify that address space, I hope the compiler blesses you. Second Bad news, the only mechanism Rust provides to supply context is that you can hand over another pointer you have and say "The same context as this". If you're lying or wrong, that's Undefined Behaviour.
Posted Jan 29, 2024 21:13 UTC (Mon)
by mb (subscriber, #50428)
[Link] (6 responses)
This is known and being worked on
https://doc.rust-lang.org/std/ptr/index.html#strict-prove...
>Rust provides *no way* to specify that address space
https://doc.rust-lang.org/std/primitive.pointer.html#meth...
Posted Jan 30, 2024 12:36 UTC (Tue)
by khim (subscriber, #9252)
[Link] (5 responses)
That method returns Can you show the full example: From what I'm seeing you may only ever turn integer that points to function
Posted Jan 30, 2024 13:25 UTC (Tue)
by tialaramex (subscriber, #21167)
[Link] (4 responses)
You can have a *mut T instead of a *const T if you want, they both exist in this API, so that's nice. But you can't safely make a function pointer this way AFAIK.
Also, you definitely can't have an actual function. Unlike C or C++, Rust's functions have unique unnameable zero size types, more similar to a C++ lambda. So you definitely can't make those, they're figments of the compiler's imagination.
let mut why_not = core::mem::needs_drop::<u8>; // Sure. Value of a Zero Size Type representing the abstract idea of this predicate
... You can make function pointers though, which are the thing you'd get in C or C++ when you try to put the function in a variable. You can undoubtedly make a function pointer from an integer and have it work. But no, you probably cannot convince Miri that's OK.
Posted Jan 30, 2024 13:50 UTC (Tue)
by khim (subscriber, #9252)
[Link] (3 responses)
No, they are not. You may write something like this:
How may I would consider it a defect in Miri and a pretty serious one. While Miri is not supposed to handle all possible Rust programs there are way to many APIs that assume that you may create function pointers from integers. But yeah, ultimately Miri is just a model of Rust, not the full Rust.
Posted Jan 30, 2024 15:52 UTC (Tue)
by tialaramex (subscriber, #21167)
[Link] (2 responses)
Posted Jan 30, 2024 16:59 UTC (Tue)
by khim (subscriber, #9252)
[Link] (1 responses)
But isn't the fact that you now have
Posted Jan 30, 2024 18:18 UTC (Tue)
by tialaramex (subscriber, #21167)
[Link]
But in my response add_or_sub is now a function item, a Zero Size variable which is always (in this particular case) our add function. ZSTs have a singular value, which is why we don't need storage space for them, the value of the (now mis-named) add_or_sub is always just add, a specific function we wrote to add numbers together. Even another function with not just the same signature, but exactly the same body and resulting machine code is a different function and so cannot be assigned to the (again, now misnamed) add_or_sub variable.
Transmute is a red herring, transmuting things into a ZST is silly. If you can *spell* the ZST then you're welcome to have one, since it has only a single value the compiler knows from the type what its value is, we don't need to "transmute" it. However like a lambda type these function items are unnameable, so you can't do that.
Function pointers are very nameable, and I'd guess you've been thinking about function pointers all along, but I wanted to emphasise that Rust does have (and make good use of) types signifying specifically the function, not just a pointer.
Yes I imagine you can take a magic integer (with some well chosen value) and transmute it into a function pointer, and (on a typical modern computer) you could call the function via that pointer and it'd work. I am not surprised that MIRI cannot justify this, and I think there aren't a lot cases where anybody has a good reason to do it, so if "Miri doesn't like it" dissuades someone from doing this that's probably good.
Posted Jan 30, 2024 12:23 UTC (Tue)
by khim (subscriber, #9252)
[Link] (5 responses)
Interesting. How do you do that with functions pointers? Heck, why do you do that with function pointers? They all are part of the same address space, your binary, why do they need that?
Posted Jan 30, 2024 19:15 UTC (Tue)
by tialaramex (subscriber, #21167)
[Link] (4 responses)
In that case ultimately, since they could just lie I'm in their hands anyway - maybe this *is* a valid pointer to an implementation of the function I wanted... but alas that implementation is in Motorola 68000 machine code and this an Intel PC so I'm screwed anyway.
My guess is that pragmatically it will just work to transmute the magic numbers into the function pointer, but that's just a guess and I can't begin to imagine how you could convince the machine that this definitely works.
Posted Feb 2, 2024 17:58 UTC (Fri)
by mathstuf (subscriber, #69389)
[Link] (3 responses)
Do you mean abstract machine here? Or perhaps the model (like MIRI)? The compiler? Because the hardware machine doesn't care and will try whatever you point the instruction pointer at (well, I suppose there are some security mechanisms that can say "no" these days).
Posted Feb 7, 2024 1:51 UTC (Wed)
by tialaramex (subscriber, #21167)
[Link] (2 responses)
Posted Feb 8, 2024 0:34 UTC (Thu)
by milesrout (subscriber, #126894)
[Link] (1 responses)
Then again I would say the same thing for the "undefined behaviour" that is dereferencing an invalid pointer. It's possible to define it perfectly well: you get the result of dereferencing it. And if that means that your system faults, then it faults. If it means you read garbage, you get garbage. But it doesn't give the compiler permission to compile out your later pointer validity checks.
Posted Feb 8, 2024 6:50 UTC (Thu)
by mb (subscriber, #50428)
[Link]
Yes. But that probably costs you most optimization opportunities and all parts of the code.
"Coding to the machine" and "getting what the hardware does" is impossible in real programs.
Posted Feb 13, 2024 22:18 UTC (Tue)
by deltragon (guest, #159552)
[Link]
Firstly:
This rename happened to distinguish between two things (using the new rust terminology here): generators, which the RFC proposes, which are more limited in scope to the specific use case of iterators (eg. they can not receive inputs on yield, only as arguments), and coroutines, which are the more powerful feature that is currently compiler-internal, where it is used as a basis to implement both generators and async block. The compiler-internal coroutines previously also used "generators" as a synonym - the renaming happened to more clearly distinguish these two.
Secondly:
This is incorrect, on two accounts.
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
- Rust heavily depends on generics. As generics are only compiled when instantiated, there would not be too much code to link against in the shared library, as the generic code would end up in the application binary.
- The ABI of a library changes even on tiny changes of structures. So you have to recompile even for minor changes of a library. We see this with C++a lot.
- Calling a library function would be UB if the ABI has changed. To allow calling library function from safe rust, the dynamic linker would need to verify that there are no backwards incompatible ABI changes in the library.
> - Rust heavily depends on generics. As generics are only compiled when instantiated, there would not be too much code to link against in the shared library, as the generic code would end up in the application binary.
Defining the Rust 2024 edition
dyn fn
mechanism which would do that, but then you need some kind of deduplication to ensure you are not creating the same thing again and again.Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
>And that is 100% the fault of languages like go and rust.
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
But the error rate *is* relevant for the rebuild-the-world argument.
Rust code has a *much* smaller CVE defect rate than C code. Therefore, the effect of rebuild the world due to CVE is much smaller than you expect.
Defining the Rust 2024 edition
Defining the Rust 2024 edition
MP3 (and other audio formats): https://crates.io/crates/symphonia (100% safe Rust)
JPEG: https://github.com/etemesi254/zune-image (with SIMD and turbo-speed, continuously fuzzed!)
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
> 2022 is the first year where memory safety vulnerabilities do not represent a majority of Android’s vulnerabilities.
> To date, there have been zero memory safety vulnerabilities discovered in Android’s Rust code.
Defining the Rust 2024 edition
Defining the Rust 2024 edition
As you know perfectly well.
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Wol
Defining the Rust 2024 edition
Defining the Rust 2024 edition
A program termination on garbage input is perfectly safe and sane.
Defining the Rust 2024 edition
Defining the Rust 2024 edition
>Say that I'm in an online game and I can crash the server making everyone else unable to play…
>that's a security bug, whatever your personal feelings might be.
Defining the Rust 2024 edition
Defining the Rust 2024 edition
I said that this particular CVE would probably not have existed, if the function was written in Rust.
Look at it!
It is a classical buffer overflow. Buffer overflows and integer overflows resulting in buffer overflows are *the* most common thing when it comes to security problems.
This bug would almost certainly not have been exploitable in Rust.
It would have been garbage data in -> garbage out (program termination).
Rust code can also have security problems, of course.
Rust only tries to make certain classes of bugs impossible or much much harder to express.
Of course you can still incorrectly implement your security checks for your door entry mechanism. But Rust ensures that it won't outright blow up right into your face with UB.
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
It doesn't make any sense to link generic libraries dynamically. It doesn't make sense in Rust and it also doesn't make sense in C++ (templates, preprocessor) or C (Preprocessor generics). These kinds of libraries always have to be statically linked.
I don't know of a single case of stdlib bug that made a world rebuild mandatory. There probably are a couple of these incidents, though.
And that is simply because certain kinds of bugs cannot happen in Rust. Because either the compiler outright refuses to compile it, or the programmer's mindset when writing unsafe Rust is completely different from programming C.
But if you have no clue about Rust, please try it. Please educate yourself. It's not a shame. It will bring you forward. And it doesn't mean that you will have to like Rust. But you will *know* it and you will not look like a fool in discussions like this anymore.
(And no, I also don't know everything about Rust and I might also have commented incorrectly at times).
Rust is very different from C. You cannot simply apply your existing C knowledge to Rust. It will automatically result in such ridiculous discussions as seen here.
Defining the Rust 2024 edition
Defining the Rust 2024 edition
const size_t MAX_BYTES=65535;
struct priv_context;
struct context {
uint8_t buffer[MAX_BYTES];
struct priv_context *private;
};
/// Allocate a new context. Note that priv_context has entries sized based on MAX_BYTES
struct context * allocate_context();
/// Supply new data to the context; if the input is more than MAX_BYTES, then the behaviour of this function is undefined
void new_data(struct context * context, uint8_t * bytes, size_t size);
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
1a) Instead of a variable-length structure, use a pointer to an aribitrarily-sized blob.
2) Have the application query/be told, at runtime, the maximum size of a given structure/field.
3) Make the structure opaque, with the API providing contstructors/destructors and access/manipulation functions.
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
There is no stable ABI for arbitrary C programs.
Yet, you demand a stable ABI for arbitrary Rust programs.
Not going to happen.
Defining the Rust 2024 edition
It's there. You can use it to have stable ABIs.
It's not correct to compare the toy-ABI of C to something as complex as Rust or even C++ and demand that if C has a semi-stable ABI, then C++ and Rust should also have them with support for the whole type system. They should not. And they can't.
Just like we can have a subset of C++ being stable.
Simple C-like Rust functions with simple Rust types going in and out can probably be made stable.
Defining the Rust 2024 edition
> It's there. You can use it to have stable ABIs.
> Defining a stable ABI for the whole language probably is impossible.
Defining the Rust 2024 edition
>on both the library provider and library user, even if both are written in Rust.
I don't really see the point.
Rust *has* a stable C ABI.
What's your point?
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
> That has value in itself, and does not threaten the current model.
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
.bp
files, you reproduce trouble with Cargo first. Why distros should be any different?Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
A compilation of a library will result in different code depending on what types the user program uses.
Of course, it can be done if the library restricts itself to trivial ABIs like C.
Defining the Rust 2024 edition
Wol
Defining the Rust 2024 edition
Functions are monomorphised per actual type (not per user program).
The actual types must meet the constraints.
But it's possible that a program can implement a foreign (exported) trait on an own type. That can be restricted by "sealing" the trait (Rust speak for making a trait un-implementable by code outside of the library).
Defining the Rust 2024 edition
> Do you not use a distribution?
Defining the Rust 2024 edition
git submodule
approach.Defining the Rust 2024 edition
> But AFAIK not only it's not made, but it's touted as a first-class citizen in terms of support.
Defining the Rust 2024 edition
x86-64
and arm
) is Tier1 target. But so are Windows 7+ and MacOS 10.12+.Defining the Rust 2024 edition
Defining the Rust 2024 edition
sudo
, BTW).Defining the Rust 2024 edition
> There's actual no reason why first-class support requires the exact same identical thing to be done everywhere.
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Wol
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
[2] These days, most of the flexibility Autotools provides has been mooted due to the nearly-complete relegation of proprietary UNIXes to the dustbin of history.
Defining the Rust 2024 edition
Defining the Rust 2024 edition
> You want to make me believe that 99% of the software present on your computer doesn't come from distribution packages?
Defining the Rust 2024 edition
Defining the Rust 2024 edition
> In the end, it seems you will argue anything and its opposite just to argue, and I have the feeling you've even forgotten what you were arguing for, so I'll stop this futile exchange.
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
> I'm sure auto*conf* could be coaxed into doing this, but I have no idea how one would automatically port any auto*make* usage to it.
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
> Now one can reasonably argue that this "requiring the complete corresponding source code to _everything_" consequence is actually a GoodThing(tm), but let's not pretend that the world solely consists of F/OSS.
Defining the Rust 2024 edition
core
/std
yet still would be able to work with them (e.g. you need to be able to declare stable Result<usize, std::stable::Error>
somehow).Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
There's another aspect I haven't gone into here which is crate size and count for rust. Most sizeable rust libraries are split up into dozens of crates for various technical and semi-technical reasons like the tooling making it easy and speeding up compile times. It wouldn't be unusual for a simple single purpose dependency to have five crates: "crate", "crate-sys" for C bindings, "crate-macros" for proc macros, "crate-util" for various extras and "thing-traits" for interoperability types with the rest of the ecosystem. Packaging and dynamically linking all of these separately would be utterly ridiculous, stable ABI or not.
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Wol
Defining the Rust 2024 edition
Defining the Rust 2024 edition
> Adding an error case to the public interface of any library does not seem to me to be the kind of update that should be made to a classical Linux distro package post release at all. Moreover, it already seems to be the kind of change that would require a major version bump per Rust's *current* semver guidelines! Today!
Say you're doing something like using a stdlib hashmap, but with different custom hashers. You can't put that code in the stdlib .so, because those user-provided types are part of their private ABI and might change. You can't put it in the user .so, because the stdlib implementation might change. You can't somehow reverse-dynamically link it or secretly pass around function pointers, because the hasher is stored in structs and on the stack by the HashMap implementation (and really wants to be inlined for performance reasons anyway). There's just no way to do it without significantly changing the semantics of the language at it's core.
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
dnf5 install pytorch
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Wol
Defining the Rust 2024 edition
Wol
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Defining the Rust 2024 edition
Function Pointer cast
Function Pointer cast
> > Rust provides *no way* to specify that address spaceFunction Pointer cast
https://doc.rust-lang.org/std/primitive.pointer.html#meth...
*const T
which is not fn
. And I couldn't find a way to turn it into fn
that Miri would like.
1. Convert fn
function pointer to usize
2. Store it into array of bytes (to_ne_bytes) then reverse it twice.
3. Convert back into interger (from_ne_bytes).
4. Turn that into function which can be called.foo
with the use of another pointer to that exact function foo
which kinda defeats the purpose: if I have an integer that's in reality address of function then it's probably address of function bar
or baz
… otherwise what's the point of all that excercise?Function Pointer cast
why_not = core::mem::needs_drop::<i8>; // Won't compile, they're both predicates with identical signatures and the exact same implementation but in theory they're different functions, thus it's a type mismatch.
> So you definitely can't make those, they're figments of the compiler's imagination.
Function Pointer cast
fn main() {
let add_or_sub = if std::env::args().len() % 2 == 0 { add } else { sub };
println!("{}", add_or_sub(42, 2));
}
fn add(x: i32, y: i32) -> i32 {
x + y
}
fn sub(x: i32, y: i32) -> i32 {
x - y
}
add_or_sub
variable even exist if function pointers are just a figments of the compiler's imagination?
What you've got there is just a function pointer, of type fn(i32, i32) -> i32
Although Rust doesn't have the uh, exciting variety of integer coercions of C, it does have coercions, and in particular what your snippet does is coerce the two ZSTs (which are distinct types) into merely function pointers (which are compatible if the functions have the same signature).
Watch what happens if we modify your code a little:
Function Pointer cast
fn main() {
let mut add_or_sub = add;
add_or_sub = if std::env::args().len() % 2 == 0 { add } else { sub };
println!("{}", add_or_sub(42, 2));
}
error[E0308]: mismatched types
--> src/main.rs:3:68
|
3 | add_or_sub = if std::env::args().len() % 2 == 0 { add } else { sub };
| ^^^ expected fn item, found a different fn item
|
= note: expected fn item `fn(_, _) -> _ {add}`
found fn item `fn(_, _) -> _ {sub}`
= note: different fn items have unique types, even if their signatures are the same
= help: consider casting both fn items to fn pointers using `as fn(i32, i32) -> i32`
Function Pointer cast
add_or_sub
which is not “a mere function pointer” means that you actually can make these (with transmute
) e.g.?Function Pointer cast
> Second Bad news, the only mechanism Rust provides to supply context is that you can hand over another pointer you have and say "The same context as this".
Function Pointer cast
Function Pointer cast
Function Pointer cast
Function Pointer cast
Function Pointer cast
Function Pointer cast
You can have that behavior today by disabling optimization in C.
Not sure, if it's possible in Rust. Probably not. Rust is quite strict about assuming no-UB.
Small corrections
> generators — recently renamed in the internal documentation to coroutines to match use of the term outside of the Rust community
> the 2021 edition becoming more stable — in the sense that new syntax will now go in the 2024 edition
New syntax that is backwards-compatible with existing editions will be added to all such editions - only incompatible changes are confined to a new edition. Also, the 2021 edition had been declared as "stabilised" back in 2021 - so it is just as stable as the 2015 edition now. Incompatible changes to be made after the 2021 edition's release had to wait until 2024, rather than being introduced into the 2021 edition, as this line implies.