Ixrec / rust-orphan-rules

An unofficial, experimental place for documenting and gathering feedback on the design problems around Rust's orphan rules
Apache License 2.0
194 stars 3 forks source link

"Official Orphans" #7

Open Ixrec opened 6 years ago

Ixrec commented 6 years ago

Sometimes, there's an impl that probably should exist, and client code probably should not have to provide it via a newtype, but neither the type's crate or the trait's crate is a good home for it. But we still want there to be a single, quasi-"official" home for these impls. As far as I know, Rust has no good answer for this case today.

This issue is for gathering examples of such use cases, and possibly discussing what a targeted solution for it might look like.

The only example I'm currently aware of is a hypothetical diesel-chrono crate.

Ixrec commented 6 years ago

I just noticed that withoutboats thinks mutually exclusive traits are part of the solution (or at least he did back in 2016). That seems significant.

gbutler69 commented 6 years ago

One possibility for "solving" this diesel-chrono type of issue is the notion of "friend" crates.

Overview

What does it look like?


// Crate t containing Trait T

// (nothing new/special required)

// Crate u containing trait U

// (nothing new/special required)

// Crate s providing structs that need impls for u::U and t::T

// main.rs/lib.rs
// (nothing new/special required)

// Make the 'baz' module's private (no visibility spec) (not pub(self), pub(nofriend), pub(super), pub(in ...mod)) items publicized to friends
pub(friend) mod baz;

...

// foo.rs (module foo)

// if nothing is designated as pub(friend) then the "friend" visible items are the same as the "pub" visible items 
// no changes to this module in that case

// foo/bar.rs (module foo::bar)

// even though this module isn't pub(friend) items within it (including structs, enums, Trait impls, fns, Inheren Methods, and sub-modules) can be pub(friend)

pub(friend) struct FooBarYep {
  // struct private elements are made "friend" accessible too
}

pub(friend) impl for FooBarYep {
  ...
}

pub(friend) fn FooBarYepProcessor( p : FooBarYep ) {

}

// not accessible to friends
struct FooBarNope {

}

// ...etc...

// baz.rs (module baz)

// nothing special needed here as the entire 'baz' module has been publicized to friends, so, if anything needs special access by a friend, it has it

// this is accessible to friends
struct Bazup {
  // all private/pub members of this accessible to friends
}

// however, if something should be "true private" (even hidden from friends, it can be declared as "pub(self)" or "pub(nofriend)"

// not accessible to friends
pub(self) struct Bazzit {
}

pub(nofriend) struct Bazout {
}

// accessible to friends 
struct Bazme {
    x : i32; // accessible to friends (because the mod has been "friended")
    pub(self) y: i32; // not accessible to friends, only accessible within the Baz mod
    pub(nofriend) z: i32; // not accessible to friends, true private even though the module was friended
    pub t : i32; // also accessible to friends
    pub(super) u : i32; // not accessible to friends
    pub(friend) v : i32; // accessible to friends (lint warning for unneeded pub(friend)
    pub(self,friend) v : 32; // only accessible to self and friends
}

// accessible to friends, but, has a lint warning for unneeded pub(friend)
pub(friend) Bazyou {
  ...
}

// main-friends.rs/lib-friends.rs (declare friend crates)

friend friend-t-impl-s-Sy impl (::t::T) for ::crate::{Baz,Foo::Bar} // provide impls of Trait T of crate t by friend crate friend-t-impl-s-Sy
friend friend-t-impl-s-Sy impl (::t::T2) for ::crate::{Baz,Foo::Bar} // provide impls of Trait T2 of crate t by friend crate friend-t-impl-s-Sy

//// friend friend-t-impl-s-Sy impl (::t::T,::t::T2) for ::crate::{Baz,Foo::Bar}

friend friend-u-impl-s-Sy impl (::u::U) for ::crate::{Baz,Foo::Bar} // provide impls of Trait U of crate u by friend crate friend-u-impl-s-Sy

// Crate friend-t-impl-s-Sy containing impls for Trait T from t for types Sy subset(a) of Sx of crate s 

use ::t::T;
use ::u::U;

use ::s::friend::Baz;
use ::s::friend::Foo::Bar;

pub timpl {
  impl T for Baz::Bazup {
  ...
  }

  impl T for Bar::FooBarYep {
  ...
  }

  // Not permitted
  //impl T for Bar::FooBarNope {
  // ...
  //}
}

// Crate friend-u-impl-s-Sz containing impls for Trait U from u for types Sz subset(b) of Sx of crate s 

// similar for u::U as for t::T above

// Crate u that uses Sy from s and wants to use the impls of T for Sy from s provided by friend-t-impl-s-Sy and for Sz from s provided by frient-t-impl-s-Sz

use ::t::T;
use ::u::U;
use ::s::Bar::FooBarYep;
use ::friend-t-impl-s-Sy::timpl;

// have impl of T for FooBarYep provided by friend-t-imp-s-Sy crate here

How do we teach this?

What does this give us?

What DOESN'T this give us?

What are the problems/uncertainties?

How would the compiler implement this?

How would cargo implement this?

Alternatives

crlf0710 commented 6 years ago

serde-json is another example. While it's the defacto standard crate for json types(and quite satisfactory), it is biased for serde usages, and not a neutral representation crate as it should be.

serde-xml is in similar situation (And might be even worse).

jplatte commented 5 years ago

I've got two features in the js_int crate, serde and rocket_04, that only serve to add trait implementations. I would argue that the latter would make a bit more sense in its own crate (the former less so because serde feels pretty 'fundamental').

luxalpa commented 8 months ago

I've got two use cases from my projects: speedy + glam and chrono / index_vec + bitcode. The latter one currently has some escape hatch (where bitcode just allows deferring to serde), but the former one is really annoying as glam is being used all over the project and new typing it is just a very big deal (currently using git subtree on glam instead).