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

Split the common usage patterns off the Alloc trait #50436

Closed
wants to merge 2 commits into from

Conversation

glandium
Copy link
Contributor

@glandium glandium commented May 3, 2018

The Alloc trait keeps functions like alloc, dealloc, realloc,
etc. and a new AllocExt trait provides alloc_one, dealloc_one,
alloc_array, etc. While there are some hypothetical benefits from
being able to have custom implementation for the latter, it's far from
the most common need, and when you need to implement generic wrappers
(which I've found to be incredibly common, to the point I've actually
started to write a custom derive for that), you still need to implement
all of them because the wrappee might be customizing them.

OTOH, if an Alloc trait implementer does try to do fancy things in
e.g. alloc_one, they're not guaranteed that dealloc_one will be
called for that allocation. There are multiple reasons for this:

  • The helpers are not used consistently. Just one example, raw_vec
    uses a mix of alloc_array, alloc/alloc_zeroed, but only uses
    dealloc.

  • Even with consistent use of e.g. alloc_array/dealloc_array, one
    can still safely convert a Vec into a Box, which would then use
    dealloc.

  • Then there are some parts of the API that just don't exist (no
    zeroed version of alloc_one/alloc_array)

So even though there are actual use cases for specialization of e.g.
alloc_one (and as a matter of fact, I do have such a need for
mozjemalloc), one is better off using a specialized allocator instead.

Actually, it's worse than that, in the rust repo, there's exactly one
use of alloc_array, and no use of alloc_one, dealloc_one,
realloc_array, dealloc_array. Not even box syntax uses alloc_one,
it uses exchange_malloc, which takes a size and align. So those
functions are more meant as a convenience for clients than for
implementers.

The AllocExt trait is implemented for all types implementing Alloc
automatically, allowing clients to opt into using its methods, while
preventing implementers from shooting themselves in the foot by trying
to do fancy things by overriding those methods (and making it easier on
people implementing proxy allocators).

@rust-highfive
Copy link
Collaborator

r? @cramertj

(rust_highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label May 3, 2018
@glandium glandium mentioned this pull request May 3, 2018
12 tasks
@cramertj
Copy link
Member

cramertj commented May 3, 2018

Assigning to someone on libs-- r? @sfackler

@rust-highfive rust-highfive assigned sfackler and unassigned cramertj May 3, 2018
The `Alloc` trait keeps functions like `alloc`, `dealloc`, `realloc`,
etc. and a new `AllocExt` trait provides `alloc_one`, `dealloc_one`,
`alloc_array`, etc.  While there are some hypothetical benefits from
being able to have custom implementation for the latter, it's far from
the most common need, and when you need to implement generic wrappers
(which I've found to be incredibly common, to the point I've actually
started to write a custom derive for that), you still need to implement
all of them because the wrappee might be customizing them.

OTOH, if an `Alloc` trait implementer does try to do fancy things in
e.g. `alloc_one`, they're not guaranteed that `dealloc_one` will be
called for that allocation. There are multiple reasons for this:

  - The helpers are not used consistently. Just one example, `raw_vec`
uses a mix of `alloc_array`, `alloc`/`alloc_zeroed`, but only uses
`dealloc`.

  - Even with consistent use of e.g. `alloc_array`/`dealloc_array`, one
can still safely convert a `Vec` into a `Box`, which would then use
`dealloc`.

  - Then there are some parts of the API that just don't exist (no
zeroed version of `alloc_one`/`alloc_array`)

So even though there are actual use cases for specialization of e.g.
`alloc_one` (and as a matter of fact, I do have such a need for
mozjemalloc), one is better off using a specialized allocator instead.

Actually, it's worse than that, in the rust repo, there's exactly one
use of `alloc_array`, and no use of `alloc_one`, `dealloc_one`,
`realloc_array`, `dealloc_array`. Not even box syntax uses `alloc_one`,
it uses `exchange_malloc`, which takes a `size` and `align`. So those
functions are more meant as a convenience for clients than for
implementers.

The `AllocExt` trait is implemented for all types implementing `Alloc`
automatically, allowing clients to opt into using its methods, while
preventing implementers from shooting themselves in the foot by trying
to do fancy things by overriding those methods (and making it easier on
people implementing proxy allocators).
@gnzlbg
Copy link
Contributor

gnzlbg commented May 4, 2018

FWIW this looks like a good step forward to me. IMO if nobody is using these we should just kill them in the future. Those who want to re-add them should make a point for them.

@glandium
Copy link
Contributor Author

glandium commented May 4, 2018

As discussed on irc, I think once we have Box<T, A> and Vec<T, A>, clients might as well use those rather than alloc_one/alloc_array.

…dealloc_array

Because generic methods can't exist in trait objects, those Self: Sized
bounds were necessary for Alloc to be usable as a trait object.

With those methods now being in a separate trait, the bounds are not
necessary anymore. In fact, since AllocExt is implemented for all
instances of Alloc, the methods are now even available to Alloc trait
objects.
@glandium
Copy link
Contributor Author

glandium commented May 5, 2018

Added #50414 on top.

@sfackler
Copy link
Member

sfackler commented May 7, 2018

@alexcrichton what do you think here? This is not the pattern used elsewhere in the standard library for convenience default impls (e.g on Iterator or Read), but I can see the issue of implementors trying to override them to do "special" things. OTOH Alloc is an unsafe trait and we could just document that the methods shouldn't be overridden.

Another approach could be adding these as methods as impls on Alloc?

impl Alloc {
    pub fn alloc_one<T>(&mut self) -> Result<Unique<T>, AllocErr> { ... }
}

@sfackler
Copy link
Member

sfackler commented May 7, 2018

Oh, I guess the direct impl approach probably wouldn't work in a non-trait object context.

@alexcrichton
Copy link
Member

I'd personally prefer to stick to the pattern we have elsewhere in the standard library which is to leave them all in the original trait. It's true that if they're overridden then wrappers won't guarantee they're called and definitions can't assume they're called either. Despite that though this remains the most ergonomic way to call them (only import one trait instead of two) and it leaves the possibility to implement speedier wrappers, even if it's hard to adhere to

@SimonSapin
Copy link
Contributor

A downside is that users potentially need to import two traits instead of just one.

@gnzlbg
Copy link
Contributor

gnzlbg commented May 8, 2018

What's the advantage of alloc_once over Box<T, Alloc> ? As in, when should one prefer alloc_once over it?


EDIT: the only utility I see is that alloc_once allows handling allocation errors.

@sfackler
Copy link
Member

sfackler commented May 8, 2018

  1. alloc_one doesn't initialize the contents
  2. alloc_one gives you control over allocatin errors
  3. alloc_one doesn't tie the pointer into a destructor in case you want to do something weird with it
  4. alloc_one exists, unlike Box<T, Alloc>

@gnzlbg
Copy link
Contributor

gnzlbg commented May 8, 2018

  1. alloc_one doesn't initialize the contents

That's indeed very useful.

@gnzlbg
Copy link
Contributor

gnzlbg commented May 8, 2018

I think that with respect to {alloc,dealloc}_{once,array} we should either:

  • specify these methods properly, e.g., by either requiring that pointers returned from alloc_once/alloc_array are deallocated by dealloc_once/dealloc_array, or by dealloc removing dealloc_once/dealloc_array.
  • deprecate these methods and let those that want them make a case for them, with removal once we have an "alternative" like Box<T, Alloc>.

Having alloc_once/alloc_array and specifying that the returned pointer should be deallocable with dealloc,dealloc_once and dealloc_array, feels weird to me (what's the point of dealloc_once/dealloc_array then?).

I like the option of requiring that alloc_once/alloc_array be deallocated with dealloc_once/dealloc_array less, because that kinds of add another invariant to pointers returned from allocators that might be worth tracking in terms of a type (e.g. NonNullFromAllocOnce...), and that complicates the design, for potentially little win.

Therefore I am more in favor of deprecation with removal once Box<T, Alloc> arrives. Box releases destructors with into_raw, and it could have a Box::try_new method that returns an error similar to how Vec::try_push works (maybe that method was actually part of the Vec::try_push RFC, I can't recall). The only thing Box won't be able to do is returning uninitialized memory, but I don't know how bad that is since one can use the other alloc methods already.

@pietroalbini
Copy link
Member

Ping from triage @sfackler! What's the status of this PR?

@sfackler
Copy link
Member

I think we want to document the semantics of how these methods interact and leave them in Alloc.

@pietroalbini pietroalbini assigned sfackler and unassigned sfackler May 21, 2018
@pietroalbini pietroalbini added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels May 21, 2018
@pietroalbini
Copy link
Member

Ping from triage @glandium! The reviewer asked for some changes, can you do them?

@glandium
Copy link
Contributor Author

Per #50436 (comment) and #50436 (comment) , let's just close this.

@glandium glandium closed this May 22, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants