Rfcs: Delegating permissions to implement Trait

Created on 19 Jun 2020  路  7Comments  路  Source: rust-lang/rfcs

Currently, it is only possible to implement traits on types if either:

a. a crate owns the type
b. a crate owns the trait

The reasoning for that, as far as I understand, is that allowing random crates to implement traits on types where they own neither we quickly end up in a situation where there are competing implementations of traits in which rust can't decide anymore which is the "right one".

This can lead to an unpleasant situation for traits that are generally useful where:

A crate implementing a common, useful, widely used, the trait has to depend on and implement traits on a large number of other crates they wish to implement the trait for - usually accompanied by a number of feature flags so users can cut down the dependency graph.

A crate implementing a common type having to depend on and pull in a number of trait-crates to implement their traits. Again usually accompanied by a number of feature flags to cut down the use of unused dependencies.

An example

The probably best example for such a case is serde, which, rightfully so, a lot of crates depend on because it offers an important trait that crates might implement.

At the time of writing, there are over 5000 crates (https://crates.io/crates/serde-json/reverse_dependencies) depending on it which to a large degree, if you just use them for functionality do not require serde at all.

(note this is not a criticism on serde or any library using its traits!)

Proposal

Allowing crates to delegate the right to implement traits on their types, or implement their trait for types to other crates would allow people to opt in / out of dependencies in an independent manner from feature flags.

In other words, a crate a could specify a list of other crates (b, c, d) that are allowed to either B, C, D are allowed to implement traits in A on types that are neither owned by A nor themselves or implement traits that neither they nor A own on types owned by A.

Since permissions would need to be explicitly delegated by a create that has the permissions themselves this would not break the initial reason of conflicting trait implementations on types.

Cargo.toml (for crate a)

delegate-trait = ["a", "b", "c"] # might need a better name but that's a detail

An example (again):

The user of the url crate could delegate permissions to a crate url-serde so that url-serde could implement Serialize and Deserialize on the types defined in url. This would remove the need for future flags and people wishing to use serialization with url can explicitly add the extra dependency.

limitations

A limitation with this minimalistic approach would be that derive based traits couldn't be delegated. However, nothing in this proposal would block adding this later on with an additional construct around deriving for foreign structs.

Most helpful comment

Note that delegation and optional dependencies/automatic features are targeting different kinds of orphan rule pain. AFAIK there's a rough consensus that we probably would like both in some form, but finalizing either of them is not a priority for now.

(and as usual, we're just recapping points made a million times in previous threads about this)

All 7 comments

See https://github.com/Ixrec/rust-orphan-rules for a more complete summary of why this stuff is the way it is and existing proposals haven't gained much traction.

This specific idea of the trait-owning crate allowlisting potential implementer crates usually runs into a "not solving the actual problem" objection like this one:

https://internals.rust-lang.org/t/revisit-orphan-rules/7795/59?u=ixrec

perhaps we could brainstorm some sort of whitelisting mechanism where diesel and chrono both declare that diesel-chrono has special permission to impl their items.

That kinda defeats the purpose. The idea is that someone should be able to make this crate without either of the parent crates having to know/care/get involved

@lxrec was faster. But here is an example that illustrates why I think this would be hard to do.

I think this could work if you only delegate permissions to a single crate. But this kind of defeats the purpose. An IMO you can just as easily use feature flags for this (and might even be more flexible with feature flags).

If you allow multiple other crates to implement: What happens if there are actually multiple crates that do implement the trait for your types somewhere in your dependency hierarchy? For example because you have both b and c as a dependency.

This issue could probably be avoided for the direct dependencies of your crate, but it gets more complicated (or impossible) for indirect dependencies. E.g. for something like this dependency tree:

- Def1 (delegates permission to crates ImplA and ImplB)
- SomeDep1
  - Def1
  - ImplA
- SomeDep2
  - Def1
  - ImplB

In this scenario you wouldn't be able to use both SomeDep1 and SomeDep2 together because ImplA and ImplB aren't be allowed to be used together.

This issue could probably be avoided for the direct dependencies of your crate, but it gets more complicated (or impossible) for indirect dependencies. E.g. for something like this dependency tree:

The exact same issue exists today with feature flagged implementation of traits. If you have a the-type create and a the-trait crate where that feature flag each other for implementations of the trait and you have two indirect dependants that include the respective feature flage.

I would go so far that this is "worse" then what would be introduced with this proposal as the implementer of the-type and the-trait do not ever need to have communicated while the owner of Def1 and ImplA and ImplB will need to have communicated (and I suspect but that's just a suspicion) will mostly be the same person.

I'd think the serde case should always be handled by optional dependencies, but optional dependencies could be improved dramatically. We could make it far easier use dependencies only when another crate requires them.

[dependencies]
serde = { version = 1.0^, optional = implicit }

I think delegation proposals envision crate authors creating micro subcrates, but doing this seems far less user friendly than the current optional dependencies scheme, not only for the crate developer, but also downstream. In short, the serde example gives a reason why rust should not do this even if it could do so.

Note that delegation and optional dependencies/automatic features are targeting different kinds of orphan rule pain. AFAIK there's a rough consensus that we probably would like both in some form, but finalizing either of them is not a priority for now.

(and as usual, we're just recapping points made a million times in previous threads about this)

I think delegation proposals envision crate authors creating micro subcrates, but doing this seems far less user friendly than the current optional dependencies scheme, not only for the crate developer but also downstream. In short, the serde example gives a reason why rust should not do this even if it could do so.

I'm curious how you come to the assessment that it's less user friendly. feature flags are entirely opaque, often the only way to find them is to inspect the Cargo.toml of a crate. Removing default features is problematic, you got to remove all and then add the ones back that you want then hope that in the future the defaults don't change and break things. Removing partial flags that are pulled in by implication is also not possible.

On the other hand, dependencies are a much more elaborate system with more capabilities, they're easily explorable, they're (mostly) independent of each other by design, they allow overwriting via the cargo toml if needed.

There is an additional burden on releasing more then one new version if a trait fundamentally changes to the level of breaking backwards compatibility.

@Ixrec I might be missing something but the discussion you linked was about allowing "random" traits to implement Features on Types, there is a very distinct difference in doing that and allowing explicit delegation of permissions. It's erveryone-has-root vs. there-is-a-sudoer-group. But I probably might miss other discussions on the topic?

The repo and the IRLO thread I linked both cover a _lot_ of ground (especially if you follow all of the other stuff they link to), including forms of allowlisting as proposing here, abandoning the orphan rules to allow "random" impls, and just about everything else in between.

Was this page helpful?
0 / 5 - 0 ratings