Live data from Hacker News

An incoherent Rust

boxyuwu.blog

161–170 of 175 posts

Re: An incoherent Rust

#161

The Rust ecosystem does a lot of what I like to call "inverse dependency injection". If a Rust library needs support for TLS, typically that library implements a feature for each existing TLS backend, and keeps first-class integration which each one. The obvious thing would be to have a TLS Trait, and have each TLS library implement that trait (i.e.: dependency injection). Because of to the orphan rule, such a trait…

Curious if they could just choose one and move on. It is really toxic to do this on every layer. For example how bad would it be if reqwest only supports rustls and is able to have less traits/generics and compiles faster

That locks users into an ecosystem that may never evolve, which can be fine but doesn't really solve one of the core issues the author was describing. It forces the ecosystem to depend on the oldest and most incumbent crates, rather than newer ones which might be better in some ways.

Re: An incoherent Rust

#162
post #120

I've been worried about this before and the problem is real. I don 't know who maintains serde but if that gets hacked its gonna be an epic supply chain attack

Serde is maintained by dtolnay, who is a very influential figure in Rust mainly through his library development. Serde, syn, anyhow etc end up being pulled in as dependencies to nearly every Rust crate. If his account was compromised, the attack surface is essentially every single other Rust crate... not ideal

Re: An incoherent Rust

#163

Does the author expect incumbents to relax language rules that grant exorbitant privilege to incumbents? The orphan rule is up there with error handling in ways Rust is a screwed up language that appeals to people who don't know what they're missing. Other systems languages aren't weak in these ways.

Could you elaborate on that error handling part? To me, Rust is the only sane language I've worked with that has error-like propagation, in that functions must explicitly state what they can return, so that you don't get some bizarre runtime error thrown because the data was invalid 15 layers deeper

Re: An incoherent Rust

#164

Earlier quoted context omitted.

There are virtually no incompatible dependencies.

The blog post gives an example of how the current approach makes dependencies incompatible. > if someone publishes an alternative to serde (say, nextserde) then all crates which have added support for serde also need to add support for nextserde. Adding support for every new serialization library in existence is unrealistic If I use serde, I cannot use a crate that only implements nextserde. If I want to use nextserd…

Let me rephrase: There are virtually no dependencies that when combined, cause a compiler error.

Re: An incoherent Rust

#165
post #91
post #83

Earlier quoted context omitted.

> so if the API is bad, you're pretty screwed. Is this really that big a downside? It encourages good APIs. The alternative of everything being public is the kind of feature that quickly becomes a big disadvantage in larger systems and teams, where saying “just don’t footgun yourself” is not a viable strategy. If there’s a workaround to achieve some goal, people will use it, and you end up with an unmaintainable mess…

There are always corner cases where you might need to do something differently. I had three memorable cases in my career: 1. Python 2.6x had a a stdlib bug where windows event logging did crash the process when the user had some rights set differently. Fix submitted but for the meantime we simply overwrote the private function and could ship. 2. Also python: scikit-learn had a primitive "print everything" strategy, b…

Three cases in your career doesn't sound like a strong counterargument to me.

I agree that escape hatches can be a good idea, though. But they should be very controlled, e.g. requiring annotations in the code, something that can be reported on by automated tooling and that can't just be done inconspicuously.

Re: An incoherent Rust

#166
post #153

Earlier quoted context omitted.

Sure, the `chrono` library in Rust had essentially the same problem. Scala is interesting. How do they resolve conflicts?

> Scala is interesting. How do they resolve conflicts? If there are multiple possible instances you get a compilation error and have to specify one explicitly (which is always an option). So you do have the problem of upgrading a dependency and getting a compilation error for something that was previously fine, but it's not a big deal in practice - what I generally do is go back to the previous version and explicitly…

Interesting, thanks for explaining.

> (although not an immediate memory corruption issue the way it could be in Rust)

Just to note, all of Rust's standard container types are designed such that they guarantee that buggy implementations of traits like `Hash` and `Ord` do not result in UB - just broken collections. :-)

Re: An incoherent Rust

#167

Earlier quoted context omitted.

Firstly, that's not a direct translation because you're making two variables and I made none at all. Rust's const is an actual constant, it's not an immutable variable. We have both, but they're different. The analogous Rust for your bit cast example would make two immutable variables that we promise have constant values, maybe: static y: u8 = 2; static x: bool = unsafe { core::mem::transmute(y) }; Of course this als…

I'm using two variables because numeric literals have the wrong type and bit_cast rejects transmutations between differently sized types. I could have written it as x = bit_cast (char{2}), but does it really make a difference? I don't know enough rust to know what's the difference between its const and c++ constexpr. It might not be a meaningful difference in C++. > So this is an impedance mismatch, you've got Roman…

> I could have written it as x = bit_cast(char{2}), but does it really make a difference?

Not really, that's also a variable. We're running into concrete differences here, which is what I was gesturing at. In C++ you've got two different things, one old and one new, and the new one does some transmutations (and is usually constexpr) while the old one does others but isn't constexpr. It's not correct to say that reinterpret_cast isn't a transmutation, for example it's the recognised way to do the "I want either a pointer or an integer of the same size" trick in C++ which is exactly that. Let me briefly explain, as much to ensure it's clear in my head as yours:

In C++ we have an integer but sometimes we're hiding a pointer in there using reinterpret_cast, in Rust we have a pointer but sometimes we're hiding an integer in there using transmute [actually core::ptr::without_provenance but that's just a transmute with a safe API]. Of course the machine code emitted is identical, because types evaporate at compile time the CPU doesn't care whether this value in a register "is" a pointer or not.

Anyway, yes the issues are the same because ultimately the machines are the same, but it's not true that C++ solved these issues the only way they could be addressed, better is possible. And in fact it would surely be a disappointment if we couldn't do any better decades later. I hope that in twenty years the Rust successor is as much better.

I don't know a way to express actual constants in C++ either. If there isn't one yet maybe C++ 29 can introduce a stuttering type qualifier co_co_const to signify that they really mean constant this time. Because constexpr is a way to get an immutable variable (with guaranteed compile time initialization and some other constraints) and in C++ we're allowed to "cast away" the immutability, we can actually just modify that variable, something like this: https://cpp.godbolt.org/z/EYnWET8sT

In contrast it doesn't mean anything to modify a constant in either language, it's not a surprise that 5 += 2 doesn't compile and so likewise Rust's core::f32::consts::PI *= 2; won't compile, and if we made our own constants we can't change those either. We can write expressions where we call into existence a temporary with our constant value, and then we mutate the temporary, but the constant itself is of course unaffected if we do this.

This can be a perf footgun, you will see newcomers write Rust where they've got a huge constant (e.g a table of 1000 32-bit floating point numbers) and they write code which just indexes into the constant in various parts of their program, if the index values are known at compile time this just optimises to the relevant 32-bit floating point number, because duh, but if they aren't it's going to shove that entire table on your stack everywhere you do this, and that's almost certainly not what you intended. It's similar to how newcomers might accidentally incur copies they didn't mean in C++ because they forgot a reference.

Re: An incoherent Rust

#168

Earlier quoted context omitted.

I'm using two variables because numeric literals have the wrong type and bit_cast rejects transmutations between differently sized types. I could have written it as x = bit_cast (char{2}), but does it really make a difference? I don't know enough rust to know what's the difference between its const and c++ constexpr. It might not be a meaningful difference in C++. > So this is an impedance mismatch, you've got Roman…

> I could have written it as x = bit_cast (char{2}), but does it really make a difference? Not really, that's also a variable. We're running into concrete differences here, which is what I was gesturing at. In C++ you've got two different things, one old and one new, and the new one does some transmutations (and is usually constexpr) while the old one does others but isn't constexpr. It's not correct to say that rein…

Is there a reason Rust would not (as it was done in the ‘good ole days’) index the table via pointer arithmetic from .data? Also, I’m assuming that because you are discussing new devs, that they are not making the implementation decision to place the table on the heap and using Rist’s subscript operator, which I would understand Rust not doing as default. I can not think of a reason that the table should ever be put on the stack for reading a single value, so that being the default seems an oddly pessimistic default. I could be missing something regarding how Rust handles literal data ‘written out’ into source though.

Re: An incoherent Rust

#169

Earlier quoted context omitted.

I'm using two variables because numeric literals have the wrong type and bit_cast rejects transmutations between differently sized types. I could have written it as x = bit_cast (char{2}), but does it really make a difference? I don't know enough rust to know what's the difference between its const and c++ constexpr. It might not be a meaningful difference in C++. > So this is an impedance mismatch, you've got Roman…

> I could have written it as x = bit_cast (char{2}), but does it really make a difference? Not really, that's also a variable. We're running into concrete differences here, which is what I was gesturing at. In C++ you've got two different things, one old and one new, and the new one does some transmutations (and is usually constexpr) while the old one does others but isn't constexpr. It's not correct to say that rein…

> https://cpp.godbolt.org/z/EYnWET8sT

I'm afraid that just C++ being C++ and you are deep into UB; you can't really modify a constexpr value at runtime, and if you cast away its constness with what is effectively a const cast you are on your own. This will print "0 3" which is obviously nonsense:

    constexpr int x = 3;
    ((int&)x)  = 0;
    char y[x];
    std::print("{}, {}",x, sizeof(y));    
The output might change according to the compiler and optimization level.

You can also move the problematic sequence into a constexpr function and invoke it in a constexpr context: the compiler will reject the cast now.

Enum constants can't become lvalues, so the following also won't compile:

    enum : int  { x = 3};
    ((int&)x)  = 0;
So I guess that's closer to the rust meaning of constant.

FWIW, notoriously you could modify numeric literals in early Fortrans and get into similar nonsense UB.

edit: in the end[1] it seems that you take exception with constexpr not being prvalues in C++. I guess it was found more convenient for them to have an address [1]. That doesn't make them less constant.

[1] or at least I think you do, it is not clear to me what you have been trying to claim in this discussion.

[2] C++ will materialize prvalues into temporaries when their address is needed (and give them an unique address), I guess it was thought to be wasteful for large constexpr objects, and avoids the rust pitfall you mentioned.

Re: An incoherent Rust

#170
post #91

Earlier quoted context omitted.

There are always corner cases where you might need to do something differently. I had three memorable cases in my career: 1. Python 2.6x had a a stdlib bug where windows event logging did crash the process when the user had some rights set differently. Fix submitted but for the meantime we simply overwrote the private function and could ship. 2. Also python: scikit-learn had a primitive "print everything" strategy, b…

Three cases in your career doesn't sound like a strong counterargument to me. I agree that escape hatches can be a good idea, though. But they should be very controlled, e.g. requiring annotations in the code, something that can be reported on by automated tooling and that can't just be done inconspicuously.

Personally, I am comfortable with Pythons "linter warning and we are all adults here" - it works well and I have never seen that somebody cried "I overwrote this private method and after an upgrade it did not work!". .Net allows it via reflection and considering that .Net Frameworks could run untrusted code it was okay that it was forbidden out of the box (since reflection was forbidden for untrusted code). But in the current world, where untrusted code does not really exist anymore? It's just legacy cruft.
Post reply on HN