From c8c486c3bd638de6d506fcfe2fb1de01ba1643ce Mon Sep 17 00:00:00 2001 From: John Ericson Date: Fri, 29 Jun 2018 19:57:27 -0400 Subject: [PATCH 01/12] extern-existential-type: Copy template --- text/0000-extern-existential-type.md | 74 ++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 text/0000-extern-existential-type.md diff --git a/text/0000-extern-existential-type.md b/text/0000-extern-existential-type.md new file mode 100644 index 00000000000..949a273dee7 --- /dev/null +++ b/text/0000-extern-existential-type.md @@ -0,0 +1,74 @@ +- Feature Name: (fill me in with a unique ident, my_awesome_feature) +- Start Date: (fill me in with today's date, YYYY-MM-DD) +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +One paragraph explanation of the feature. + +# Motivation +[motivation]: #motivation + +Why are we doing this? What use cases does it support? What is the expected outcome? + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +Explain the proposal as if it was already included in the language and you were teaching it to another Rust programmer. That generally means: + +- Introducing new named concepts. +- Explaining the feature largely in terms of examples. +- Explaining how Rust programmers should *think* about the feature, and how it should impact the way they use Rust. It should explain the impact as concretely as possible. +- If applicable, provide sample error messages, deprecation warnings, or migration guidance. +- If applicable, describe the differences between teaching this to existing Rust programmers and new Rust programmers. + +For implementation-oriented RFCs (e.g. for compiler internals), this section should focus on how compiler contributors should think about the change, and give examples of its concrete impact. For policy RFCs, this section should provide an example-driven introduction to the policy, and explain its impact in concrete terms. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +This is the technical portion of the RFC. Explain the design in sufficient detail that: + +- Its interaction with other features is clear. +- It is reasonably clear how the feature would be implemented. +- Corner cases are dissected by example. + +The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work. + +# Drawbacks +[drawbacks]: #drawbacks + +Why should we *not* do this? + +# Rationale and alternatives +[alternatives]: #alternatives + +- Why is this design the best in the space of possible designs? +- What other designs have been considered and what is the rationale for not choosing them? +- What is the impact of not doing this? + +# Prior art +[prior-art]: #prior-art + +Discuss prior art, both the good and the bad, in relation to this proposal. +A few examples of what this can include are: + +- For language, library, cargo, tools, and compiler proposals: Does this feature exist in other programming languages and what experience have their community had? +- For community proposals: Is this done by some other community and what were their experiences with it? +- For other teams: What lessons can we learn from what other communities have done here? +- Papers: Are there any published papers or great posts that discuss this? If you have some relevant papers to refer to, this can serve as a more detailed theoretical background. + +This section is intended to encourage you as an author to think about the lessons from other languages, provide readers of your RFC with a fuller picture. +If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other languages. + +Note that while precedent set by other languages is some motivation, it does not on its own motivate an RFC. +Please also take into consideration that rust sometimes intentionally diverges from common language features. + +# Unresolved questions +[unresolved]: #unresolved-questions + +- What parts of the design do you expect to resolve through the RFC process before this gets merged? +- What parts of the design do you expect to resolve through the implementation of this feature before stabilization? +- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? From 9f90e0fb21b8c4faa87fd7ebd69efbc890a7b7d1 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Sat, 30 Jun 2018 12:43:26 -0400 Subject: [PATCH 02/12] extern-existential-type: Initial draft --- text/0000-extern-existential-type.md | 217 ++++++++++++++++++++++----- 1 file changed, 180 insertions(+), 37 deletions(-) diff --git a/text/0000-extern-existential-type.md b/text/0000-extern-existential-type.md index 949a273dee7..898b19d70c7 100644 --- a/text/0000-extern-existential-type.md +++ b/text/0000-extern-existential-type.md @@ -1,74 +1,217 @@ -- Feature Name: (fill me in with a unique ident, my_awesome_feature) -- Start Date: (fill me in with today's date, YYYY-MM-DD) +- Feature Name: extern-existential-type +- Start Date: 2018-6-29 - RFC PR: (leave this empty) - Rust Issue: (leave this empty) # Summary [summary]: #summary -One paragraph explanation of the feature. +A version of #2071's `existential type` where the definition can live in a different crate than the declaration. +This is a crucial tool untangling for untangling dependencies within `std` and other libraries at the root of the ecosystem concerning global resources. # Motivation [motivation]: #motivation -Why are we doing this? What use cases does it support? What is the expected outcome? +We have a number of situations where one create defines an interface, and a different crate implements that interface with a canonical singleton: -# Guide-level explanation -[guide-level-explanation]: #guide-level-explanation + - [`core::alloc::GlobalAlloc`](https://doc.rust-lang.org/nightly/core/alloc/trait.GlobalAlloc.html), chosen with [`#[global_allocator]`](https://doc.rust-lang.org/1.23.0/unstable-book/language-features/global-allocator.html) + - `panic_fmt` chosen with [`#[panic_implementation]`](https://github.com/rust-lang/rfcs/blob/master/text/2070-panic-implementation.md) + - The OOM hook, modified with [`std::alloc::{set,take}_alloc_error_hook`](https://doc.rust-lang.org/nightly/std/alloc/fn.set_alloc_error_hook.html) + - `std::hash:RandomState`, if https://github.com/rust-lang/rust/pull/51846 is merged, the `hashmap_random_keys` lang item. + - [`log::Log`](https://docs.rs/log/0.4.3/log/trait.Log.html) set with https://docs.rs/log/0.4.3/log/fn.set_logger.html + +Each of these is an instance of the same general pattern. +But the solutions are all ad-hoc and distinct, adding burdening the user of Rust and rustc with needless extra information, and preventing more rapid prototyping. +They also incur a run-time cost due to dynamism and indirection, which can lead to initializing bugs or bloat in space-constrained environments. + +`existential type` just covers the deferred definition of a type, and not the singleton value itself, but that is enough. For example, with global allocation: + +```rust +// in `alloc` + +pub extern existential type Heap: Copy + Alloc + Default; + +struct Box; + +impl Box { + fn new_in(a: A) { .. } +} + +impl Box { + fn new() { Self::new_in(Default::default()) } +} +``` + +```rust +// in `jemalloc` + +struct JemallocHeap; + +impl Default for JemallocHeap { + fn default() { JemallocHeap } +} + +impl Alloc for JemallocHeap { + fn alloc(stuff: Stuff) -> Thing { + ... + } +} + +impl existential type alloc::Heap = JemallocHeap; +``` + +```rust +// in crate making an rust-implemented local allocator global + +static GLOBALIZED_LOCAL_ALLOC = MyConcurrentLocalAlloc(..): + +strict MyConcurrentLocalAllocHeap; -Explain the proposal as if it was already included in the language and you were teaching it to another Rust programmer. That generally means: +impl Default for MyConcurrentLocalAllocHeap { + fn default() { MyConcurrentLocalAllocHeap } +} -- Introducing new named concepts. -- Explaining the feature largely in terms of examples. -- Explaining how Rust programmers should *think* about the feature, and how it should impact the way they use Rust. It should explain the impact as concretely as possible. -- If applicable, provide sample error messages, deprecation warnings, or migration guidance. -- If applicable, describe the differences between teaching this to existing Rust programmers and new Rust programmers. +impl Alloc for MyConcurrentLocalAllocHeap { + fn alloc(stuff: Stuff) -> Thing { + GLOBALIZED_LOCAL_ALLOC.alloc(stuff) + } +} -For implementation-oriented RFCs (e.g. for compiler internals), this section should focus on how compiler contributors should think about the change, and give examples of its concrete impact. For policy RFCs, this section should provide an example-driven introduction to the policy, and explain its impact in concrete terms. +impl existential type alloc::Heap = JemallocHeap; +``` + +By defining traits for each bit of deferred functionary, we can likewise cover each of the other use-cases. +This frees the compiler and programmer to forget about the specific instances and just learn the general pattern. +This is especially important for `log`, which isn't a sysroot crate and thus isn't known to the compiler at all at the moment. +It would be very hard to justify special casing it with e.g. another attribute as the problem is solved today, when it needs none at the moment. +As for the cost concerns with the existing techniques, No code is generated until the `impl existential type` is created, similar to with generic types, so there is no run-time cost whatsoever. + +Many of the mechanisms I listed above are on the verge of stabilization. +I don't want to appear to by tying things up forever, so the design I've picked strives to be simple + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +It's best to understand this feature in terms of regular `existential type`. +Type checking when *using* `pub extern existential type` works exactly the same way: +The type is opaque except for it's bounds, and no traits can be implemented for it. +```rust +pub extern existential type Foo: Baz; +existential type Bar: Baz; +// both are used the same way if we +``` +C and C++ programmers too will be familiar with the remote definition aspect of this from those language's "forward declarations" and their use in header files. + +On the definition side, since it is explicitly defined (somewhere), there are no inference constraints on items in the same module as the declaration or definition. + +One more interesting difference is the scope of where the type is transparent vs opaque: i.e. where can we see the type's definition, or only it's bounds. +Just as in C where one gets: +```rust +struct Foo; + +// I know nothing about Foo + +struct Foo { int a; }; + +// Ah now I do +``` +when the `impl existential type` is in scope, the `existential existential type` can becomes transparent and behaves as if the declaration and definition were put together into a normal type alias. +The definer can decide how gets to take advantage of it by making the definition public or not. +```rust +pub impl existential type alloc::Foo = Bar; // the big reveal +impl existential type alloc::Foo = Bar; // the tiny reveal +``` +There are no restrictions on the type of publicity compared to other items. + +Only one crate in the build plan can define the `pub extern existential type`. +Unlike the trait system, there are no orphan restrictions that ensure crates can always be composed: +any crate is free to define the `pub extern existential type`, as long is it isn't used with another that also does, in which case the violation will only be caught when building a crate that depends on both (or if one of the crates depends on the other). +This is not very nice, but exactly like "lang items" and the annotations that exist today, +so it is nothing worse. + +As mentioned in the introduction, code gen can be reasoned about by comparing with generic and inlining). +We cannot generate for code for generic items until they are instantiated. +Likewise, imagine that everything that uses an `pub extern existential type` gets an extra parameter, +and then when the `impl pub extern existential type` is defined, we go back and eliminate that parameter by substituting the actual definition. +Only then can we generate code. +This is why from the root crate of compilation (the binary, static library, or other such "final" component), the dependency closure must contain an `impl existential type` for every `pub extern existential type` that's actually used. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -This is the technical portion of the RFC. Explain the design in sufficient detail that: +`pub extern existential type` can also be formally defined in reference to `existential type`. +As explained in the guide-level explanation, +```rust +(pub )? extern existential type : ; +``` +creates an existential type alias that behaves just like a `use`d `existential type : ` defined in another modules so it's opaque, while +```rust +(pub )? impl existential type = ; +``` +reveals the definition of the existential type alias at `path` as if it was a regular type alias. -- Its interaction with other features is clear. -- It is reasonably clear how the feature would be implemented. -- Corner cases are dissected by example. +There is a post-hoc coherence rule that every used `pub extern existential type` contains exactly one `impl exisitential type` definition within the entire build-plan of crates. +"used" here can be roughly defined by tracing all identifiers through their bindings, but should make intuitive sense. -The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work. +There is nothing preventing private `extern existential type`, or a `impl extern type` in the same module as its `extern existential type`. +Both these situations make the feature useless and could be linted, but are well defined from the rules above. # Drawbacks [drawbacks]: #drawbacks -Why should we *not* do this? +Niko Matsakis has expressed concerned about this being abused because singletons are bad. +I agree singletons are bad, but the connection between existential types and singletons is not obvious at first sight (imagine if we did the same thing with `static`s directly), so I hope this will be sufficiently difficult to abuse. +Even if we deem this very toxic, better all the use cases I listed above be white-listed and use same mechanism used for consistency, then use a bunch of separate solutions. + +Stabilization of many annotations and APIs I call out in the motivation section is imminent, and yes this would delay that a bit if we opted to do this and then rewrite those APIs to use it. + +As per the "prior art" section, something like Haskell's backpack is wholly superior. +But as stabilization of the status quo is imminent, I wanted to pick something easier to implement and closer to existing rust features mentally/pedagogically. # Rationale and alternatives [alternatives]: #alternatives -- Why is this design the best in the space of possible designs? -- What other designs have been considered and what is the rationale for not choosing them? -- What is the impact of not doing this? +- Of course, we can always do nothing and just keep the grab bag of ad-hoc solutions we have today, and leave log with just a imperative dynamic solution. -# Prior art -[prior-art]: #prior-art +- We could continue special-casing and white-listing in the compiler the use-cases I give in the motivation, but at least use the same sort of annotation for all of them for consistency. + But that still requires leaving out `log`, or special casing it for the first time. + As bad as I agree singletons are, I imagine a few yet-unforseen use-cases for this (e.g. for peripherals for bare-metal programming, which are morally singletons) arising. + So that leaves other special-cases we would need to add in the future. + +- I mention just doing this would delay stabilization. + But, we could also retrofit those annotations as desugaring into this feature so as not to delay it. + This keeps around the crust in the compiler forever, but at least we can deprecate the annotations in the next epoch. + I don't think it's worth it to bend over backwards for something that is still unstable, and consider it unwise to so "whiplash" the ecosystem telling them to use one stable thing and then immediate another, but for those that really want to stabilize stuff, this is an option. + +- In many cases, the `extern existential type` would just be a ZST proxy used in a default argument. + If we could add default arguments to existing type parameters, then the original items wouldn't need an abstract stand-in. + @eddyb and others have thought very hard about this approach for many years, and it doesn't seem possible, however. + +See the prior art section below for context on the last two. -Discuss prior art, both the good and the bad, in relation to this proposal. -A few examples of what this can include are: +- We couldn't do exactly ML's functors for this problem, because people both want to import `std` without passing in a global allocator, yet also be able to use `std` with different global allocators. -- For language, library, cargo, tools, and compiler proposals: Does this feature exist in other programming languages and what experience have their community had? -- For community proposals: Is this done by some other community and what were their experiences with it? -- For other teams: What lessons can we learn from what other communities have done here? -- Papers: Are there any published papers or great posts that discuss this? If you have some relevant papers to refer to, this can serve as a more detailed theoretical background. +- I opted out from proposing exactly Haskell' backpack because of the perceived time pressure mentioned above, but it's straightforward to imagine `extern existential type` being convenient sugar for some more general parameterized module system, similar to `impl Trait` and regular `existential type`. -This section is intended to encourage you as an author to think about the lessons from other languages, provide readers of your RFC with a fuller picture. -If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other languages. +# Prior art +[prior-art]: #prior-art -Note that while precedent set by other languages is some motivation, it does not on its own motivate an RFC. -Please also take into consideration that rust sometimes intentionally diverges from common language features. +The basic idea come from the "functors" of the ML family of languages, where a module is given explicit parameters, like +```rust +mod Foo<...> { ... } +``` +in Rust syntax, and then those modules can be applied like functions. +```rust +mod Bar = Foo<...>; +``` + +More appropriate is Haskell's new [backpack](https://plv.mpi-sws.org/backpack/) modules system, where the parameterization is not explicit in the code (`use`d modules may be resolved or just module signatures, in which case they act as parameters), and Cabal (the Cargo equivalent), auto-applies everything. +This would work for Rust, and in fact is wholly better in that modules can be applied multiple times like ML, there is no syntactic overhead of manual applications, and Cabal, with it's knowledge of who needs what, can complain early if something would be defined twice / two different instantiations do not unify as a downstream crate needs. +[That latter issue problem is not possible here under the single-instantiation rule.] # Unresolved questions [unresolved]: #unresolved-questions -- What parts of the design do you expect to resolve through the RFC process before this gets merged? -- What parts of the design do you expect to resolve through the implementation of this feature before stabilization? -- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? +- The exact syntax. "existential" is a temporary stand-in from #2071, which I just use here for consistency. I personally prefer "abstract" FWIW. + +- Should Cargo have some knowledge of `extern abstract type` declarations and definitions from the get-go so it can catch invalid build plans early? From 463029935a1c693aee732e7f840da9b27abf2921 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Sat, 30 Jun 2018 15:11:59 -0400 Subject: [PATCH 03/12] Fix some of @Centril's comments --- text/0000-extern-existential-type.md | 61 +++++++++++++++------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/text/0000-extern-existential-type.md b/text/0000-extern-existential-type.md index 898b19d70c7..1b94764d478 100644 --- a/text/0000-extern-existential-type.md +++ b/text/0000-extern-existential-type.md @@ -6,13 +6,13 @@ # Summary [summary]: #summary -A version of #2071's `existential type` where the definition can live in a different crate than the declaration. +A version of https://github.com/rust-lang/rfcs/pull/2071's `existential type` where the definition can live in a different crate than the declaration, rather than the same module. This is a crucial tool untangling for untangling dependencies within `std` and other libraries at the root of the ecosystem concerning global resources. # Motivation [motivation]: #motivation -We have a number of situations where one create defines an interface, and a different crate implements that interface with a canonical singleton: +We have a number of situations where one crate defines an interface, and a different crate implements that interface with a canonical singleton: - [`core::alloc::GlobalAlloc`](https://doc.rust-lang.org/nightly/core/alloc/trait.GlobalAlloc.html), chosen with [`#[global_allocator]`](https://doc.rust-lang.org/1.23.0/unstable-book/language-features/global-allocator.html) - `panic_fmt` chosen with [`#[panic_implementation]`](https://github.com/rust-lang/rfcs/blob/master/text/2070-panic-implementation.md) @@ -21,15 +21,21 @@ We have a number of situations where one create defines an interface, and a diff - [`log::Log`](https://docs.rs/log/0.4.3/log/trait.Log.html) set with https://docs.rs/log/0.4.3/log/fn.set_logger.html Each of these is an instance of the same general pattern. -But the solutions are all ad-hoc and distinct, adding burdening the user of Rust and rustc with needless extra information, and preventing more rapid prototyping. -They also incur a run-time cost due to dynamism and indirection, which can lead to initializing bugs or bloat in space-constrained environments. +But the solutions are all ad-hoc and distinct, burdening the user of Rust and rustc with extra work remembering/implementing, and preventing more rapid prototyping. -`existential type` just covers the deferred definition of a type, and not the singleton value itself, but that is enough. For example, with global allocation: +They also incur a run-time cost due to dynamism and indirection, which can lead to initialization bugs or bloat in space-constrained environments. +In the annotation case, there's essentially an extra `extern { fn special_name(..); }` whose definition the annotation generates. +This isn't easily inlined outside of LTO, and even then would prohibit rustc's own optimizations going into affect. +The `set`-method based ones involve mutating a `static mut` or equivalent with a function or trait object, and thus can basically never be inlined away. +So there's the overhead of the initialization, and then one or two memory dereferences to get the implementation function's actual address. +The potential bugs are due to not `set`ing before the resource is needed, a manual task because there's static way to prevent accessing the resource while it isn't set. + +The `extern existential type` feature just covers the deferred definition of a type, and not the singleton itself, but that is actually enough. For example, with global allocation: ```rust -// in `alloc` +// In `alloc` -pub extern existential type Heap: Copy + Alloc + Default; +pub extern existential type Heap: Copy + Alloc + Default + Send + Sync; struct Box; @@ -43,33 +49,31 @@ impl Box { ``` ```rust -// in `jemalloc` +// In `jemalloc` +#[deriving(Default, Copy)] struct JemallocHeap; -impl Default for JemallocHeap { - fn default() { JemallocHeap } -} - impl Alloc for JemallocHeap { fn alloc(stuff: Stuff) -> Thing { ... } } -impl existential type alloc::Heap = JemallocHeap; +extern existential type alloc::Heap = JemallocHeap; ``` ```rust -// in crate making an rust-implemented local allocator global +// In a crate making an rust-implemented local allocator global. -static GLOBALIZED_LOCAL_ALLOC = MyConcurrentLocalAlloc(..): +struct MyConcurrentLocalAlloc(..); -strict MyConcurrentLocalAllocHeap; +impl Alloc for MyConcurrentLocalAlloc; -impl Default for MyConcurrentLocalAllocHeap { - fn default() { MyConcurrentLocalAllocHeap } -} +static GLOBALIZED_LOCAL_ALLOC = MyConcurrentLocalAlloc(..): + +#[deriving(Default, Copy)] +struct MyConcurrentLocalAllocHeap; impl Alloc for MyConcurrentLocalAllocHeap { fn alloc(stuff: Stuff) -> Thing { @@ -77,14 +81,14 @@ impl Alloc for MyConcurrentLocalAllocHeap { } } -impl existential type alloc::Heap = JemallocHeap; +extern existential type alloc::Heap = JemallocHeap; ``` -By defining traits for each bit of deferred functionary, we can likewise cover each of the other use-cases. +By defining traits for each bit of deferred functionality (`Alloc`, `Log`), we can likewise cover each of the other use-cases. This frees the compiler and programmer to forget about the specific instances and just learn the general pattern. This is especially important for `log`, which isn't a sysroot crate and thus isn't known to the compiler at all at the moment. It would be very hard to justify special casing it with e.g. another attribute as the problem is solved today, when it needs none at the moment. -As for the cost concerns with the existing techniques, No code is generated until the `impl existential type` is created, similar to with generic types, so there is no run-time cost whatsoever. +As for the cost concerns with the existing techniques, No code is generated until the `extern existential type` is created, similar to with generic types, so there is no run-time cost whatsoever. Many of the mechanisms I listed above are on the verge of stabilization. I don't want to appear to by tying things up forever, so the design I've picked strives to be simple @@ -115,11 +119,11 @@ struct Foo { int a; }; // Ah now I do ``` -when the `impl existential type` is in scope, the `existential existential type` can becomes transparent and behaves as if the declaration and definition were put together into a normal type alias. +when the `extern existential type` is in scope, the `existential existential type` can becomes transparent and behaves as if the declaration and definition were put together into a normal type alias. The definer can decide how gets to take advantage of it by making the definition public or not. ```rust -pub impl existential type alloc::Foo = Bar; // the big reveal -impl existential type alloc::Foo = Bar; // the tiny reveal +pub extern existential type alloc::Foo = Bar; // the big reveal +extern existential type alloc::Foo = Bar; // the tiny reveal ``` There are no restrictions on the type of publicity compared to other items. @@ -134,7 +138,7 @@ We cannot generate for code for generic items until they are instantiated. Likewise, imagine that everything that uses an `pub extern existential type` gets an extra parameter, and then when the `impl pub extern existential type` is defined, we go back and eliminate that parameter by substituting the actual definition. Only then can we generate code. -This is why from the root crate of compilation (the binary, static library, or other such "final" component), the dependency closure must contain an `impl existential type` for every `pub extern existential type` that's actually used. +This is why from the root crate of compilation (the binary, static library, or other such "final" component), the dependency closure must contain an `extern existential type` for every `pub extern existential type` that's actually used. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -146,7 +150,7 @@ As explained in the guide-level explanation, ``` creates an existential type alias that behaves just like a `use`d `existential type : ` defined in another modules so it's opaque, while ```rust -(pub )? impl existential type = ; +(pub )? extern existential type = ; ``` reveals the definition of the existential type alias at `path` as if it was a regular type alias. @@ -162,6 +166,7 @@ Both these situations make the feature useless and could be linted, but are well Niko Matsakis has expressed concerned about this being abused because singletons are bad. I agree singletons are bad, but the connection between existential types and singletons is not obvious at first sight (imagine if we did the same thing with `static`s directly), so I hope this will be sufficiently difficult to abuse. Even if we deem this very toxic, better all the use cases I listed above be white-listed and use same mechanism used for consistency, then use a bunch of separate solutions. +Also, by forcing the use of a trait in the bounds of the `extern existential type`, we hopefully nudge the user in the direction of providing a non-singleton-based way of accomplishing the same task (e.g. local allocators in addition to the global allocator). Stabilization of many annotations and APIs I call out in the motivation section is imminent, and yes this would delay that a bit if we opted to do this and then rewrite those APIs to use it. @@ -212,6 +217,6 @@ This would work for Rust, and in fact is wholly better in that modules can be ap # Unresolved questions [unresolved]: #unresolved-questions -- The exact syntax. "existential" is a temporary stand-in from #2071, which I just use here for consistency. I personally prefer "abstract" FWIW. +- The exact syntax. "existential" is a temporary stand-in from https://github.com/rust-lang/rfcs/pull/2071, which I just use here for consistency. I personally prefer "abstract" FWIW. - Should Cargo have some knowledge of `extern abstract type` declarations and definitions from the get-go so it can catch invalid build plans early? From aef049c6a141dafe3f555048dffbfb855df4ce37 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Mon, 2 Jul 2018 08:01:19 -0400 Subject: [PATCH 04/12] Fix more comments --- text/0000-extern-existential-type.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/text/0000-extern-existential-type.md b/text/0000-extern-existential-type.md index 1b94764d478..6a55bd3431e 100644 --- a/text/0000-extern-existential-type.md +++ b/text/0000-extern-existential-type.md @@ -87,22 +87,23 @@ extern existential type alloc::Heap = JemallocHeap; By defining traits for each bit of deferred functionality (`Alloc`, `Log`), we can likewise cover each of the other use-cases. This frees the compiler and programmer to forget about the specific instances and just learn the general pattern. This is especially important for `log`, which isn't a sysroot crate and thus isn't known to the compiler at all at the moment. -It would be very hard to justify special casing it with e.g. another attribute as the problem is solved today, when it needs none at the moment. -As for the cost concerns with the existing techniques, No code is generated until the `extern existential type` is created, similar to with generic types, so there is no run-time cost whatsoever. +It would be very hard to justify special casing `log` in rustc with e.g. another attribute as the problem is solved today, when it needs none at the moment. +As for the cost concerns with the existing techniques, no code is generated until the `extern existential type` is created, similar to with generic types, so there is no run-time cost whatsoever. -Many of the mechanisms I listed above are on the verge of stabilization. -I don't want to appear to by tying things up forever, so the design I've picked strives to be simple +Many of the mechanisms listed in this RFC above are on the verge of stabilization. +This RFC doesn't want to appear to by tying things up forever, so the design strives to be simple while still being general enough. +This ought to also be forwards compatible with the more comprehensive solutions as described in the alternatives section. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation It's best to understand this feature in terms of regular `existential type`. Type checking when *using* `pub extern existential type` works exactly the same way: -The type is opaque except for it's bounds, and no traits can be implemented for it. +The type is opaque except for its bounds, and no traits can be implemented for it. ```rust pub extern existential type Foo: Baz; existential type Bar: Baz; -// both are used the same way if we +// both are used the same way in other modules ``` C and C++ programmers too will be familiar with the remote definition aspect of this from those language's "forward declarations" and their use in header files. @@ -119,19 +120,23 @@ struct Foo { int a; }; // Ah now I do ``` -when the `extern existential type` is in scope, the `existential existential type` can becomes transparent and behaves as if the declaration and definition were put together into a normal type alias. -The definer can decide how gets to take advantage of it by making the definition public or not. +when the `extern existential type` is in scope, the `existential existential type` becomes transparent and behaves as if the declaration and definition were put together into a normal type alias. +The definer can decide how one downstream gets to take advantage of it by making the definition public or not. ```rust pub extern existential type alloc::Foo = Bar; // the big reveal extern existential type alloc::Foo = Bar; // the tiny reveal ``` -There are no restrictions on the type of publicity compared to other items. +private allows the item to be used (as some definition is needed), but while no one downstream knows its true definition. like regular `existential type`. +Public allows downstream to choose between staying agnostic for increased flexibility, or peaking the hind the veil for extra functionality. +(e.g. maybe it wants to require the global allocator by jemalloc to use some special jemalloc-specific debug output.) +There are no restrictions on the type of publicity on the definitions compared to other items. Only one crate in the build plan can define the `pub extern existential type`. Unlike the trait system, there are no orphan restrictions that ensure crates can always be composed: any crate is free to define the `pub extern existential type`, as long is it isn't used with another that also does, in which case the violation will only be caught when building a crate that depends on both (or if one of the crates depends on the other). -This is not very nice, but exactly like "lang items" and the annotations that exist today, -so it is nothing worse. +This is not very nice, but exactly like "lang items" and the annotations that exist for this purpose today, +so it is nothing worse than what's current about to be stabilized. +There is no natural orphan rules for this feature (or alternatively, regular `existential type` can be seen as this with the orphan rule that it must be defined in the same module), so this is expected. As mentioned in the introduction, code gen can be reasoned about by comparing with generic and inlining). We cannot generate for code for generic items until they are instantiated. From adcf48c392db8ff5d01b57a7912e8eb5c4928d5b Mon Sep 17 00:00:00 2001 From: John Ericson Date: Mon, 2 Jul 2018 08:17:18 -0400 Subject: [PATCH 05/12] Fix more issues, including adding section links --- text/0000-extern-existential-type.md | 33 ++++++++++++++++++---------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/text/0000-extern-existential-type.md b/text/0000-extern-existential-type.md index 6a55bd3431e..c44c3c11282 100644 --- a/text/0000-extern-existential-type.md +++ b/text/0000-extern-existential-type.md @@ -92,7 +92,7 @@ As for the cost concerns with the existing techniques, no code is generated unti Many of the mechanisms listed in this RFC above are on the verge of stabilization. This RFC doesn't want to appear to by tying things up forever, so the design strives to be simple while still being general enough. -This ought to also be forwards compatible with the more comprehensive solutions as described in the alternatives section. +This ought to also be forwards compatible with the more comprehensive solutions as described in the [alternatives](#alternatives) section. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -137,6 +137,7 @@ any crate is free to define the `pub extern existential type`, as long is it isn This is not very nice, but exactly like "lang items" and the annotations that exist for this purpose today, so it is nothing worse than what's current about to be stabilized. There is no natural orphan rules for this feature (or alternatively, regular `existential type` can be seen as this with the orphan rule that it must be defined in the same module), so this is expected. +See the first alternative for how we can use Cargo to ameliorate this. As mentioned in the introduction, code gen can be reasoned about by comparing with generic and inlining). We cannot generate for code for generic items until they are instantiated. @@ -168,19 +169,22 @@ Both these situations make the feature useless and could be linted, but are well # Drawbacks [drawbacks]: #drawbacks -Niko Matsakis has expressed concerned about this being abused because singletons are bad. -I agree singletons are bad, but the connection between existential types and singletons is not obvious at first sight (imagine if we did the same thing with `static`s directly), so I hope this will be sufficiently difficult to abuse. -Even if we deem this very toxic, better all the use cases I listed above be white-listed and use same mechanism used for consistency, then use a bunch of separate solutions. +Niko Matsakis has expressed concerns about this being abused because singletons are bad. +Singletons are indeed bad, but the connection between existential types and singletons is not obvious at first sight (imagine if we had deferred definition mechanism with `static`s directly), which hopefully will make this be sufficiently difficult to abuse. +Even if we deem this very toxic, better all the use cases I listed above be white-listed and use same mechanism used for consistency (and one that is cost-free at run time), than use a bunch of separate solutions. Also, by forcing the use of a trait in the bounds of the `extern existential type`, we hopefully nudge the user in the direction of providing a non-singleton-based way of accomplishing the same task (e.g. local allocators in addition to the global allocator). -Stabilization of many annotations and APIs I call out in the motivation section is imminent, and yes this would delay that a bit if we opted to do this and then rewrite those APIs to use it. +Stabilization of many annotations and APIs called out in the [motivation](#motivation) section is imminent, and yes this would delay that a bit if we opted to do this and then rewrite those APIs to use it. -As per the "prior art" section, something like Haskell's backpack is wholly superior. +As per the [prior art](#prior-art) section, something like Haskell's backpack is wholly superior. But as stabilization of the status quo is imminent, I wanted to pick something easier to implement and closer to existing rust features mentally/pedagogically. # Rationale and alternatives [alternatives]: #alternatives +- We can additionally mandate that `Cargo.toml` include all `extern existential type` declarations and definitions, and Cargo reject any build plan where they don't match 1-1. + This ameliorates the crate composition issue in practice for the vast majority of users using Cargo (even just `Cargo.toml`s). + - Of course, we can always do nothing and just keep the grab bag of ad-hoc solutions we have today, and leave log with just a imperative dynamic solution. - We could continue special-casing and white-listing in the compiler the use-cases I give in the motivation, but at least use the same sort of annotation for all of them for consistency. @@ -190,14 +194,14 @@ But as stabilization of the status quo is imminent, I wanted to pick something e - I mention just doing this would delay stabilization. But, we could also retrofit those annotations as desugaring into this feature so as not to delay it. - This keeps around the crust in the compiler forever, but at least we can deprecate the annotations in the next epoch. + This keeps around the crust in the compiler forever, but at least we can deprecate the annotations in the next edition. I don't think it's worth it to bend over backwards for something that is still unstable, and consider it unwise to so "whiplash" the ecosystem telling them to use one stable thing and then immediate another, but for those that really want to stabilize stuff, this is an option. - In many cases, the `extern existential type` would just be a ZST proxy used in a default argument. If we could add default arguments to existing type parameters, then the original items wouldn't need an abstract stand-in. @eddyb and others have thought very hard about this approach for many years, and it doesn't seem possible, however. -See the prior art section below for context on the last two. +See the [prior art](#prior-art) section below for context on the last two. - We couldn't do exactly ML's functors for this problem, because people both want to import `std` without passing in a global allocator, yet also be able to use `std` with different global allocators. @@ -208,15 +212,22 @@ See the prior art section below for context on the last two. The basic idea come from the "functors" of the ML family of languages, where a module is given explicit parameters, like ```rust -mod Foo<...> { ... } +mod foo<...> { ... } ``` in Rust syntax, and then those modules can be applied like functions. ```rust -mod Bar = Foo<...>; +mod bar = foo<...>; ``` More appropriate is Haskell's new [backpack](https://plv.mpi-sws.org/backpack/) modules system, where the parameterization is not explicit in the code (`use`d modules may be resolved or just module signatures, in which case they act as parameters), and Cabal (the Cargo equivalent), auto-applies everything. -This would work for Rust, and in fact is wholly better in that modules can be applied multiple times like ML, there is no syntactic overhead of manual applications, and Cabal, with it's knowledge of who needs what, can complain early if something would be defined twice / two different instantiations do not unify as a downstream crate needs. +This would work for Rust, and in fact is wholly better: + + - It is more expressive because modules can be applied multiple times like ML and unlike this. + + - There is still no syntactic overhead of manual applications at use sites, like this and unlike ML. + + - Cabal, with it's knowledge of who needs what, can still complain early if something would be defined twice / two different instantiations do not unify as a downstream crate needs, like the first alternative. + [That latter issue problem is not possible here under the single-instantiation rule.] # Unresolved questions From cd7e2d42f2327092c30b737a12afb1f88815e489 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Mon, 2 Jul 2018 08:33:04 -0400 Subject: [PATCH 06/12] extern-existential-type: Tweak abstract to match PR description --- text/0000-extern-existential-type.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-extern-existential-type.md b/text/0000-extern-existential-type.md index c44c3c11282..d3a06dd752b 100644 --- a/text/0000-extern-existential-type.md +++ b/text/0000-extern-existential-type.md @@ -6,7 +6,7 @@ # Summary [summary]: #summary -A version of https://github.com/rust-lang/rfcs/pull/2071's `existential type` where the definition can live in a different crate than the declaration, rather than the same module. +An extension of https://github.com/rust-lang/rfcs/pull/2071's `existential type` where the definition can live in a different crate than the declaration, rather than the same module. This is a crucial tool untangling for untangling dependencies within `std` and other libraries at the root of the ecosystem concerning global resources. # Motivation From 553631ceb6459f9b60553c972574f99409119337 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Mon, 2 Jul 2018 09:06:22 -0400 Subject: [PATCH 07/12] extern-existential-type: Fix some links --- text/0000-extern-existential-type.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0000-extern-existential-type.md b/text/0000-extern-existential-type.md index d3a06dd752b..4bfb859d16e 100644 --- a/text/0000-extern-existential-type.md +++ b/text/0000-extern-existential-type.md @@ -6,7 +6,7 @@ # Summary [summary]: #summary -An extension of https://github.com/rust-lang/rfcs/pull/2071's `existential type` where the definition can live in a different crate than the declaration, rather than the same module. +An extension of [#2071](https://github.com/rust-lang/rfcs/pull/2071)'s `existential type` where the definition can live in a different crate than the declaration, rather than the same module. This is a crucial tool untangling for untangling dependencies within `std` and other libraries at the root of the ecosystem concerning global resources. # Motivation @@ -17,7 +17,7 @@ We have a number of situations where one crate defines an interface, and a diffe - [`core::alloc::GlobalAlloc`](https://doc.rust-lang.org/nightly/core/alloc/trait.GlobalAlloc.html), chosen with [`#[global_allocator]`](https://doc.rust-lang.org/1.23.0/unstable-book/language-features/global-allocator.html) - `panic_fmt` chosen with [`#[panic_implementation]`](https://github.com/rust-lang/rfcs/blob/master/text/2070-panic-implementation.md) - The OOM hook, modified with [`std::alloc::{set,take}_alloc_error_hook`](https://doc.rust-lang.org/nightly/std/alloc/fn.set_alloc_error_hook.html) - - `std::hash:RandomState`, if https://github.com/rust-lang/rust/pull/51846 is merged, the `hashmap_random_keys` lang item. + - [`std::collections::hash_map::RandomState`](https://doc.rust-lang.org/std/collections/hash_map/struct.RandomState.html), if https://github.com/rust-lang/rust/pull/51846 is merged, the `hashmap_random_keys` lang item. - [`log::Log`](https://docs.rs/log/0.4.3/log/trait.Log.html) set with https://docs.rs/log/0.4.3/log/fn.set_logger.html Each of these is an instance of the same general pattern. @@ -233,6 +233,6 @@ This would work for Rust, and in fact is wholly better: # Unresolved questions [unresolved]: #unresolved-questions -- The exact syntax. "existential" is a temporary stand-in from https://github.com/rust-lang/rfcs/pull/2071, which I just use here for consistency. I personally prefer "abstract" FWIW. +- The exact syntax. "existential" is a temporary stand-in from [#2071](https://github.com/rust-lang/rfcs/pull/2071), which I just use here for consistency. I personally prefer "abstract" FWIW. - Should Cargo have some knowledge of `extern abstract type` declarations and definitions from the get-go so it can catch invalid build plans early? From d421c5cf2602b64c59057c47861fa457678415d4 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Mon, 2 Jul 2018 09:07:05 -0400 Subject: [PATCH 08/12] extern-existential-type: Word smith --- text/0000-extern-existential-type.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-extern-existential-type.md b/text/0000-extern-existential-type.md index 4bfb859d16e..609d14a53d1 100644 --- a/text/0000-extern-existential-type.md +++ b/text/0000-extern-existential-type.md @@ -219,7 +219,7 @@ in Rust syntax, and then those modules can be applied like functions. mod bar = foo<...>; ``` -More appropriate is Haskell's new [backpack](https://plv.mpi-sws.org/backpack/) modules system, where the parameterization is not explicit in the code (`use`d modules may be resolved or just module signatures, in which case they act as parameters), and Cabal (the Cargo equivalent), auto-applies everything. +More appropriate is Haskell's new [backpack](https://plv.mpi-sws.org/backpack/) module system, where the parameterization is not explicit in the code (`use`d modules may be resolved or just module signatures, in which case they act as parameters), and Cabal (the Cargo equivalent), auto-applies everything. This would work for Rust, and in fact is wholly better: - It is more expressive because modules can be applied multiple times like ML and unlike this. From 133bf032cf7d47dff71fe1dac911b0623e6d80e6 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Mon, 2 Jul 2018 09:09:12 -0400 Subject: [PATCH 09/12] extern-existential-type: prettify `set_logger` link, remove "." --- text/0000-extern-existential-type.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-extern-existential-type.md b/text/0000-extern-existential-type.md index 609d14a53d1..321833750ad 100644 --- a/text/0000-extern-existential-type.md +++ b/text/0000-extern-existential-type.md @@ -17,8 +17,8 @@ We have a number of situations where one crate defines an interface, and a diffe - [`core::alloc::GlobalAlloc`](https://doc.rust-lang.org/nightly/core/alloc/trait.GlobalAlloc.html), chosen with [`#[global_allocator]`](https://doc.rust-lang.org/1.23.0/unstable-book/language-features/global-allocator.html) - `panic_fmt` chosen with [`#[panic_implementation]`](https://github.com/rust-lang/rfcs/blob/master/text/2070-panic-implementation.md) - The OOM hook, modified with [`std::alloc::{set,take}_alloc_error_hook`](https://doc.rust-lang.org/nightly/std/alloc/fn.set_alloc_error_hook.html) - - [`std::collections::hash_map::RandomState`](https://doc.rust-lang.org/std/collections/hash_map/struct.RandomState.html), if https://github.com/rust-lang/rust/pull/51846 is merged, the `hashmap_random_keys` lang item. - - [`log::Log`](https://docs.rs/log/0.4.3/log/trait.Log.html) set with https://docs.rs/log/0.4.3/log/fn.set_logger.html + - [`std::collections::hash_map::RandomState`](https://doc.rust-lang.org/std/collections/hash_map/struct.RandomState.html), if https://github.com/rust-lang/rust/pull/51846 is merged, the `hashmap_random_keys` lang item + - [`log::Log`](https://docs.rs/log/0.4.3/log/trait.Log.html) set with [`log::set_logger`](https://docs.rs/log/0.4.3/log/fn.set_logger.html) Each of these is an instance of the same general pattern. But the solutions are all ad-hoc and distinct, burdening the user of Rust and rustc with extra work remembering/implementing, and preventing more rapid prototyping. From a9074484e33cf03088d4d666c8363a8937f9e9ac Mon Sep 17 00:00:00 2001 From: John Ericson Date: Mon, 2 Jul 2018 11:44:09 -0400 Subject: [PATCH 10/12] existential-existential-type: Get rid of remmenants of `impl existential type` --- text/0000-extern-existential-type.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0000-extern-existential-type.md b/text/0000-extern-existential-type.md index 321833750ad..c29b5c58177 100644 --- a/text/0000-extern-existential-type.md +++ b/text/0000-extern-existential-type.md @@ -142,7 +142,7 @@ See the first alternative for how we can use Cargo to ameliorate this. As mentioned in the introduction, code gen can be reasoned about by comparing with generic and inlining). We cannot generate for code for generic items until they are instantiated. Likewise, imagine that everything that uses an `pub extern existential type` gets an extra parameter, -and then when the `impl pub extern existential type` is defined, we go back and eliminate that parameter by substituting the actual definition. +and then when the `pub extern existential type = ...` is defined, we go back and eliminate that parameter by substituting the actual definition. Only then can we generate code. This is why from the root crate of compilation (the binary, static library, or other such "final" component), the dependency closure must contain an `extern existential type` for every `pub extern existential type` that's actually used. @@ -160,10 +160,10 @@ creates an existential type alias that behaves just like a `use`d `existential t ``` reveals the definition of the existential type alias at `path` as if it was a regular type alias. -There is a post-hoc coherence rule that every used `pub extern existential type` contains exactly one `impl exisitential type` definition within the entire build-plan of crates. +There is a post-hoc coherence rule that every used `pub extern existential type` contains exactly one `extern exisitential type` definition within the entire build-plan of crates. "used" here can be roughly defined by tracing all identifiers through their bindings, but should make intuitive sense. -There is nothing preventing private `extern existential type`, or a `impl extern type` in the same module as its `extern existential type`. +There is nothing preventing private `extern existential type`, or a `extern extern existential type` in the same module as its `extern existential type`. Both these situations make the feature useless and could be linted, but are well defined from the rules above. # Drawbacks From d9bf55b3877e14ad7116d982e7fd566deb7522bd Mon Sep 17 00:00:00 2001 From: John Ericson Date: Mon, 2 Jul 2018 11:48:52 -0400 Subject: [PATCH 11/12] extern-existential-type: Move some stuff to drawbacks @Jethrogb is right in https://github.com/rust-lang/rfcs/pull/2492#discussion_r199513755, this doesn't belong in the guide-level explanation. --- text/0000-extern-existential-type.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/text/0000-extern-existential-type.md b/text/0000-extern-existential-type.md index c29b5c58177..14262b4a657 100644 --- a/text/0000-extern-existential-type.md +++ b/text/0000-extern-existential-type.md @@ -134,10 +134,6 @@ There are no restrictions on the type of publicity on the definitions compared t Only one crate in the build plan can define the `pub extern existential type`. Unlike the trait system, there are no orphan restrictions that ensure crates can always be composed: any crate is free to define the `pub extern existential type`, as long is it isn't used with another that also does, in which case the violation will only be caught when building a crate that depends on both (or if one of the crates depends on the other). -This is not very nice, but exactly like "lang items" and the annotations that exist for this purpose today, -so it is nothing worse than what's current about to be stabilized. -There is no natural orphan rules for this feature (or alternatively, regular `existential type` can be seen as this with the orphan rule that it must be defined in the same module), so this is expected. -See the first alternative for how we can use Cargo to ameliorate this. As mentioned in the introduction, code gen can be reasoned about by comparing with generic and inlining). We cannot generate for code for generic items until they are instantiated. @@ -169,6 +165,11 @@ Both these situations make the feature useless and could be linted, but are well # Drawbacks [drawbacks]: #drawbacks +The fact that not all crates can compose with this, due to duplicate or missing definitions per declaration, is not very nice. +However, this is exactly like "lang items" and the annotations that exist for this purpose today, so it is nothing worse than what's currently about to be stabilized. +There is no natural orphan rules for this feature (or alternatively, regular `existential type` can be seen as this with the orphan rule that it must be defined in the same module), so this is expected. +See the first alternative for how we can use Cargo to ameliorate this. + Niko Matsakis has expressed concerns about this being abused because singletons are bad. Singletons are indeed bad, but the connection between existential types and singletons is not obvious at first sight (imagine if we had deferred definition mechanism with `static`s directly), which hopefully will make this be sufficiently difficult to abuse. Even if we deem this very toxic, better all the use cases I listed above be white-listed and use same mechanism used for consistency (and one that is cost-free at run time), than use a bunch of separate solutions. From 51851224669166a2747200bf9c45508ad485b2aa Mon Sep 17 00:00:00 2001 From: John Ericson Date: Mon, 2 Jul 2018 13:46:13 -0400 Subject: [PATCH 12/12] extern-existential-types: Clarify allocation examples @sfackler made the excellent point of how we should be able to use `jemalloc` without committing to it as the global allocator. Fixing that with both `jemalloc` and the local allocator examples. --- text/0000-extern-existential-type.md | 29 ++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/text/0000-extern-existential-type.md b/text/0000-extern-existential-type.md index 14262b4a657..523dcb15068 100644 --- a/text/0000-extern-existential-type.md +++ b/text/0000-extern-existential-type.md @@ -31,7 +31,6 @@ So there's the overhead of the initialization, and then one or two memory derefe The potential bugs are due to not `set`ing before the resource is needed, a manual task because there's static way to prevent accessing the resource while it isn't set. The `extern existential type` feature just covers the deferred definition of a type, and not the singleton itself, but that is actually enough. For example, with global allocation: - ```rust // In `alloc` @@ -44,12 +43,12 @@ impl Box { } impl Box { - fn new() { Self::new_in(Default::default()) } + fn new() { Self::new_in(Default::default()) } } ``` - ```rust -// In `jemalloc` +// In `jemalloc`, which has one instance but may not be the global allacator +// for Rust. #[deriving(Default, Copy)] struct JemallocHeap; @@ -59,16 +58,29 @@ impl Alloc for JemallocHeap { ... } } +``` +```rust +// In `jemalloc-global`, which "promotes" jemalloc as the global allocator. + +use jemalloc::JemallocHeap; extern existential type alloc::Heap = JemallocHeap; ``` - ```rust -// In a crate making an rust-implemented local allocator global. +// In `my-concurrent-local-allocator`, which is `Send + Sync` but can have many +// instances. + +struct MyConcurrentLocalAlloc(...); -struct MyConcurrentLocalAlloc(..); +impl Alloc for MyConcurrentLocalAlloc { + ... +}; +``` +```rust +// In `my-concurrent-local-allocator-global`, which "promotes" a static of +// `MyConcurrentLocalAlloc` as the global allocator -impl Alloc for MyConcurrentLocalAlloc; +use my_concurrent_local_allocator::MyConcurrentLocalAlloc; static GLOBALIZED_LOCAL_ALLOC = MyConcurrentLocalAlloc(..): @@ -83,6 +95,7 @@ impl Alloc for MyConcurrentLocalAllocHeap { extern existential type alloc::Heap = JemallocHeap; ``` +Note how these examples show how foreign and pure-Rust implementations of the global resource can both be used. By defining traits for each bit of deferred functionality (`Alloc`, `Log`), we can likewise cover each of the other use-cases. This frees the compiler and programmer to forget about the specific instances and just learn the general pattern.