Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A fundamental assumption of this repo is false #22

Open
steventrouble opened this issue Aug 6, 2023 · 0 comments
Open

A fundamental assumption of this repo is false #22

steventrouble opened this issue Aug 6, 2023 · 0 comments

Comments

@steventrouble
Copy link

steventrouble commented Aug 6, 2023

One Big Issue

This document relies on the following assumption:

Rust chooses to enforce coherence

But alas, this assumption is not true.

Rust supports incoherence

Rust actually supports incoherence in many cases. Consider the following code:

trait Foo {
    fn init(&self);
}

trait Bar {
    fn init(&self);
}

struct MyStruct;

impl Foo for MyStruct {
    fn get(&self) {
        println!("Foo.init()");
    }
}

impl Bar for MyStruct {
    fn init(&self) {
        println!("Bar.init()");
    }
}

fn main() {
    let struct = MyStruct;
    struct.init(); // Does not compile
    Bar::init(&struct);
}

Here, there are two valid implementations of init() here, and the developer had to disambiguate which the compiler should use. This is a type of incoherence, and it is fully supported in Rust.

What's going on here?

It is difficult to avoid some amount of incoherence in a language that a) allows multiple implementations on a struct, and b) allows calling implementations without choosing a specific implementation.

To implement these features, you must combine disparate namespaces into a single shared namespace at some point. You can do this by either: a) allowing overlapping symbol names and requiring disambiguation, or b) requiring symbol names to be globally unique.

As seen above, the Rust developers clearly decided that a) disambiguation should be supported for the example above. They also allow disambiguation in the cases of generics, such as in the example of ::<T>collect(). So the question is not "Why does Rust not support incoherence?" because the question has a false assumption. Rust does support incoherence in some capacity.

The real question is: Why does Rust support some kinds of incoherence but not others?

Function Incoherence vs Trait Incoherence

In Rust, there is an easy way to disambiguate functions: using the trait name. When a developer needs to call a function from a trait, it's easy for them to disambiguate it by using the Trait::func(myStruct) syntax.

Impls, however, are not named. Oops! Rust just dumps all valid implementations of a trait into a global namespace and includes them all whenever a trait is used. Because of this, there is no easy way for developers to specify which trait impl should be used, and therefore trait impls are required to be globally unique.

"Ok, but what about..."

In "What's wrong with incoherence", this repo mentions a few potential problems with incoherence, and proposes a solution to these problems. It then criticizes that solution. That is a classic straw man fallacy: arguing against a solution that you yourself proposed. It's a fallacy because better alternatives might exist that you neglected mention.

In particular, generics in Rust avoid all the problems with your solution. You specify generics at the call site, not at the crate level, which avoids all the issues the straw man has. Because why would anyone disambiguate function calls at the crate level? That doesn't even make sense.

What would it take?

To enable disambiguating trait impls, every impl would have to have an identifier, and for backwards compatibility these would have to be auto-generated somehow if the code didn't specify one.

This would be an enormous undertaking, and the auto-generated identiers would make the crate imports section quite messy in the rare cases you needed to disambiguate traits.

The Real Reason

So let's get to the heart of the issue. Why is impl incoherence forbidden, while all other important types of incoherence are allowed?

Occam's razor tells us that the simplest valid explanation is usually the best one:

Because it's hard and we don't want to do it.

Let's stop spreading the lie that incoherence is inherently wrong. Many programming languages, including Rust, support disambiguation in many other cases, so it can't be that bad. Instead, let's just be honest and admit that we don't have the time or energy to implement it for this particular case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant