Live data from Hacker News

An incoherent Rust

boxyuwu.blog

51–60 of 175 posts

Re: An incoherent Rust

#51
post #10

Note the use case - someone wants to have the ability to replace a base-level crate such as serde. When something near the bottom needs work, should there be a process for fixing it, which is a people problem? Or should there be a mechanism for bypassing it, which is a technical solution to a people problem? This is one of the curses of open source. The first approach means that there will be confrontations which mus…

It's a social problem that's created by a technical problem. In many languages, if you want to integrate package A with package B, you can make and share a package AB, which people can reuse. That scales, and facilitates reuse, and avoids either package having to support everything. In Rust, if the integration involves traits, integration between package A and package B must happen either in A or in B. That creates a…

Other than duck-typed languages (and I count Go as basically that), which languages actually provide this feature?

AFAIK, it’s not really very common to be able to extend foreign types with new interfaces, especially not if you own neither.

C++ can technically do it using partial specialization, but it’s not exactly nice, and results in UB via ODR violation when it goes wrong (say you have two implementations of a `std::hash` specialization, etc.). And it only works for interfaces that are specifically designed to be specialized this way - not for vanilla dynamic dispatch, say.

Re: An incoherent Rust

#52

Earlier quoted context omitted.

there is no good way to handle colliding implementations. Both from parallel crates and due to changes over time. Without it you can have many many additional forms of breakage. Worse you can have "new" breakage between two 3rd party crates without either of them changing due to some impl in a common ancestor changing (e.g. std) and this affecting two wild card implementations in each, now leading to an overlap. When…

> In many other ecosystems it's not uncommon to run into having issues where certain libraries can't be used together at all. The same problem exists in Rust, but from the other side. If I use serde for serialization I am effectively locked in to using crates that implement serde traits (or do newtype hacks to define them myself). If I want to use something more niche than serde, I essentially lose access to all the…

Newtypes aren’t hacks, they’re perfectly acceptable in my opinion. Especially if you’re willing to also use a crate like `derive_more`.

Re: An incoherent Rust

#53
post #9
post #4

There's a well-known (and frequently encouraged) workaround for the orphan rule: Create a wrapper type. Let's say you have one library with: pub struct TypeWithSomeSerialization { /* public fields here */ } And you want to define a custom serialization. In this case, you can write: pub struct TypeWithDifferentSerialization(TypeWithSomeSerialization) Then you just implement Serialize and Deserialize for TypeWithDiffer…

The gotcha is what happens when TypeWithSomeSerialization is not something you’re using directly but is contained within SomeOtherTypeWithSomeSerialization which you are using directly. Then things get messy.

We can't say with certainty how an unspecified in-the-future library might work, so I'm going to use serde as a stand-in.

You can implement `Serialize` for a wrapper type and still serialize `SomeOtherTypeWithSomeSerialization` (which might be used by the type being wrapper directly or indirectly) differently. It might not be derivable, of course, but "I don't want the default" sort of makes that a given.

Re: An incoherent Rust

#54

Earlier quoted context omitted.

It's a social problem that's created by a technical problem. In many languages, if you want to integrate package A with package B, you can make and share a package AB, which people can reuse. That scales, and facilitates reuse, and avoids either package having to support everything. In Rust, if the integration involves traits, integration between package A and package B must happen either in A or in B. That creates a…

Other than duck-typed languages (and I count Go as basically that), which languages actually provide this feature? AFAIK, it’s not really very common to be able to extend foreign types with new interfaces, especially not if you own neither. C++ can technically do it using partial specialization, but it’s not exactly nice, and results in UB via ODR violation when it goes wrong (say you have two implementations of a `s…

C# isnt a duck type language (well, you can do that via dynamic keyword, but I don't know who would do that typically).

Most integration libraries in Nuget (aka c#'s cargo) are AB type libraries.

E.g. DI Container: Autofac Messaging Library: MediatR Integration: MediatR.Extensions.Autofac.DependencyInjection

There are many examples of popular libraries like this in that world.

Re: An incoherent Rust

#55
post #15

This is one of the (several?) things that make me very worried about Rust long-term. I love the language, and reach for it even when it sometimes isn't the most appropriate thing. But reading some of the made-up syntax in the "Removing Coherence" section makes my head hurt. When I used to write Scala, I accepted the fact that I don't have a background in type/set/etc. theory, and that there were some facets of the la…

> I wonder if C++ has some hairy concepts and syntax today on par with Rust's more difficult parts. … … … … Unqualified name lookup has been challenging in C++ since even before C++11. Overload resolution rules are so painful that it took me weeks to review a patch simply because I had to back out of trying to make sense of the rules in the standard. There's several slightly different definitions of initialization. I…

Unless you're writing a compiler, you should require the author of the patch to explain why it works.

Re: An incoherent Rust

#56

Earlier quoted context omitted.

there is no good way to handle colliding implementations. Both from parallel crates and due to changes over time. Without it you can have many many additional forms of breakage. Worse you can have "new" breakage between two 3rd party crates without either of them changing due to some impl in a common ancestor changing (e.g. std) and this affecting two wild card implementations in each, now leading to an overlap. When…

> In many other ecosystems it's not uncommon to run into having issues where certain libraries can't be used together at all. The same problem exists in Rust, but from the other side. If I use serde for serialization I am effectively locked in to using crates that implement serde traits (or do newtype hacks to define them myself). If I want to use something more niche than serde, I essentially lose access to all the…

for me that is a completely different problem,

one you solve when initially writing code (so you can properly account for it and control it)

instead of a problem which can blow up when you update a package for a very pressing security fix

in the end it a question what is more important, stability or the option to monkey patch functionality into your dependencies without changing them

and given that you can always non-monkey patch crates (rust makes vendoring dep. relatively easy in case upstream doesn't fix things) I prefer the stability aspect (through if you do patch crates you re-introduce many of the issues in a different place, with the main difference of there being a chance to upstream you changes)

Re: An incoherent Rust

#57
It is fundamentally difficult to have an “ecosystem”.

Would much rather see a bunch of libraries that implement everything for a given use case like web-dev, embedded etc.

Unfortunately this is hard to do in rust because it is hard to implement the low level primitives.

Language’s goal should be to make building things easier imo. It should be simple to build a serde or a tokio.

From what I have seen in rust, people tend to over-engineer a single library to the absolute limit instead just building a bunch of libraries and moving on.

As an example, if it is easy to build a btreemap then you don’t have to have a bunch of traits from a bunch of different libraries pre-implemented on it. You can just copy it, adapt it a bit and move on.

Then you can have a complete thing that gives you everything you need to write a web server and it just works

Re: An incoherent Rust

#58

Earlier quoted context omitted.

> I wonder if C++ has some hairy concepts and syntax today on par with Rust's more difficult parts. … … … … Unqualified name lookup has been challenging in C++ since even before C++11. Overload resolution rules are so painful that it took me weeks to review a patch simply because I had to back out of trying to make sense of the rules in the standard. There's several slightly different definitions of initialization. I…

Unless you're writing a compiler, you should require the author of the patch to explain why it works.

This was a patch for the compiler implementation of the changes to the standard.

Re: An incoherent Rust

#59

It is fundamentally difficult to have an “ecosystem”. Would much rather see a bunch of libraries that implement everything for a given use case like web-dev, embedded etc. Unfortunately this is hard to do in rust because it is hard to implement the low level primitives. Language’s goal should be to make building things easier imo. It should be simple to build a serde or a tokio. From what I have seen in rust, people…

So what I mean is, having a big library that implements the whole problem is better. Because then each part of that library is simple. Then I can copy paste some parts and change some others to create an alternative library. And it is better for the user of the thing because it is simple.

Having everything compatible with everything else and having everything implement every case means every individual part is over-complicated. So it is bad no matter how you combine it together.

Re: An incoherent Rust

#60
I feel like encapsulation and composition are in strong tension, and this is one place where it boils over.

I've written a decent bit of Rust, and am currently messing around with Zig. So the comparison is pretty fresh on my mind:

In Rust, you can have private fields. In Zig all fields are public. The consequences are pretty well shown with how they print structs: In Rust, you derive Debug, which is a macro that implements the Debug trait at the definition site. In Zig, the printing function uses reflection to enumerate the provided struct's fields, and creates a print string based on that. So Rust has the display logic at the definition site, while Zig has the logic at the call site.

It's similar with hash maps: in Rust you derive/implement the Hash and PartialEq trait, in Zig you provide the hash and eq function at the call site.

Each one has pretty stark downsides: Zig - since everything is public, you can't guarantee that your invariants are valid. Anyone can mess around with your internals. Rust - once a field is private (which is the convention), nobody else can mess with the internals. This means outside modules can't access internal state, so if the API is bad, you're pretty screwed.

Honestly, I'm not sure if there is a way to resolve this tension.

EDIT: one more thought: Zig vs Rust also shows up with how object destruction is handled. In Rust you implement a Drop trait, so each object can only have one way to be destroyed. In Zig you use defer/errdefer, so you can choose what type of destructor runs, but this also means you can mess up destruction in subtle ways.

Post reply on HN