Open Enderlook opened 11 months ago
This would be problematic for Weak
, specifically Weak
s created by Weak::new(_in)
, which do not have a backing allocation. fn Weak::allocator
(if/when it exists) could return Option<&A>
instead of &A
, with None
indicating that there is no backing allocation. Similarly, fn Weak::new_in
could take no arguments, since it doesn't need to have an allocator (We probably couldn't just generalize Weak::new
unless type parameter defaults affecting type inference becomes possible).
This would also require into_raw_with_allocator
/from_raw_in
to be changed on all four types. into_raw_with_allocator
could just be removed, with into_raw
being generalized. from_raw_in
would have to change in the same way as Weak::new_in
, for the same reason.
There's also something to be said for consistency; Box
and Vec
currently do not do this, and given their guarantees I'm not sure they could. On the other hand, Rc/Arc are unique in that they are conceptually shared where other heap-allocation types are uniquely owned, which may make "consistency" less convincing.
This would also mean that Arc
/Rc
wouldn't support being used interchangeably with A
and &A
as allocators
This would be problematic for
Weak
, specificallyWeak
s created byWeak::new(_in)
, which do not have a backing allocation.fn Weak::allocator
(if/when it exists) could returnOption<&A>
instead of&A
, withNone
indicating that there is no backing allocation. Similarly,fn Weak::new_in
could take no arguments, since it doesn't need to have an allocator (We probably couldn't just generalizeWeak::new
unless type parameter defaults affecting type inference becomes possible).
Of course, these changes would also be applied to Weak
for consistency, otherwise, it wouldn't make much sense. I should have been more explicit.
This would also require
into_raw_with_allocator
/from_raw_in
to be changed on all four types.into_raw_with_allocator
could just be removed, withinto_raw
being generalized.from_raw_in
would have to change in the same way asWeak::new_in
, for the same reason.
Yeah, a slightly inconvenient of this issue is that it's a breaking change. Although Rust has editions, so maybe the change could be between editions. I'm not sure if editions also include type changes or just compiler features...
There's also something to be said for consistency;
Box
andVec
currently do not do this, and given their guarantees I'm not sure they could. On the other hand, Rc/Arc are unique in that they are conceptually shared where other heap-allocation types are uniquely owned, which may make "consistency" less convincing.
I don't know if that is a convincing argument against this. After all Box
and Vec
has single ownership, and so the allocator is only stored once. Instead Arc
and Rc
can be cloned multiple times, so with the current approach, you are also duplicating the allocator, incurring unnecessary memory (and probably CPU) costs, (plus the downside that several methods require to implement Clone
in the allocator, which could be completely avoided).
Also, maybe they should, though as you said, their guarantees make it much more difficult. Probably not worth it for those types.
This would also mean that
Arc
/Rc
wouldn't support being used interchangeably withA
and&A
as allocators
Not sure if I understand that sentence, but I don't see a problem on it. If you use different allocators, you would always have to use generics, how does this feature impact on that specific issue?
This would also require
into_raw_with_allocator
/from_raw_in
to be changed on all four types.into_raw_with_allocator
could just be removed, withinto_raw
being generalized.from_raw_in
would have to change in the same way asWeak::new_in
, for the same reason.Yeah, a slightly inconvenient of this issue is that it's a breaking change. Although Rust has editions, so maybe the change could be between editions. I'm not sure if editions also include type changes or just compiler features...
I don't see how it would be a breaking change, actually. All of the _with_allocator
/_in
functions are unstable, so "breaking changes" to them are fine until they are stabilized, and do not require any edition-gating. Generalizing into_raw
should not be a breaking change[^1]. Unless you mean generalizing from_raw
? I don't know that this could be done over an edition easily[^2], but I suppose it could be done.
This would also mean that
Arc
/Rc
wouldn't support being used interchangeably withA
and&A
as allocatorsNot sure if I understand that sentence, but I don't see a problem on it. If you use different allocators, you would always have to use generics, how does this feature impact on that specific issue?
It is a fairly common pattern to do something similar the following to deallocate a heap-allocated container without dropping the allocator itself:
// let boxed: Box<T, A> = ...;
let (ptr, alloc) = Box::into_raw_with_allocator(boxed);
let boxed: Box<T, &A> = unsafe { Box::from_raw_in(ptr, &alloc) };
drop(boxed); // Deallocate using `impl<A: Allocator> Allocator for &A`
// use alloc for other things
This pattern is used in impl From<Box<T, A>> for Arc<T, A>
and impl From<Vec<T, A>> for Arc<[T], A>
, for example.
If Arc<T, A>
holds A
in its heap allocation (or its heap allocation's layout in any other way depends on A
), then this pattern cannot be used with Arc
as it can with Box
and Vec
. (Well, really the whole idea behind this proposal means that this pattern cannot be used with Arc
). IMO the existence of impl<A: Allocator + ?Sized> Allocator for &A
is a strong hint that &A
can be used interchangeably with A
for purposes of (de)allocation, which would mean that Arc<T, A>
having the heap allocation's layout depend on the layout of A
is unexpected.
[^1]: Because Arc
already has a generic parameter, there's no String
-like issue where the allocator parameter cannot be inferred, since users are generally either: 1. specifying Arc<T>
which explicitly uses the default, or 2. already using type inference to resolve the T
in Arc<T>
anyway, so the allocator should not cause issues I think.
[^2]: AFAIK there are currently no path-resolution differences between editions; making Arc::from_raw
resolve to different items in different editions would require support for this to be newly added to the compiler. Another way I could think to implement it would be to add the A
parameter but have it "hard-coded" to Global
on old editions, which Is also unprecedented AFAIK. The "best" way to resolve it would be "type parameter defaults affect inference" as discussed with Weak::new
, which is also not currently possible.
I don't see how it would be a breaking change, actually. All of the
_with_allocator
/_in
functions are unstable, so "breaking changes" to them are fine until they are stabilized, and do not require any edition-gating.
Great.
It is a fairly common pattern to do something similar the following to deallocate a heap-allocated container without dropping the allocator itself:
// let boxed: Box<T, A> = ...; let (ptr, alloc) = Box::into_raw_with_allocator(boxed); let boxed: Box<T, &A> = unsafe { Box::from_raw_in(ptr, &alloc) }; drop(boxed); // Deallocate using `impl<A: Allocator> Allocator for &A` // use alloc for other things
This pattern is used in
impl From<Box<T, A>> for Arc<T, A>
andimpl From<Vec<T, A>> for Arc<[T], A>
, for example.If
Arc<T, A>
holdsA
in its heap allocation (or its heap allocation's layout in any other way depends onA
), then this pattern cannot be used withArc
as it can withBox
andVec
. (Well, really the whole idea behind this proposal means that this pattern cannot be used withArc
). IMO the existence ofimpl<A: Allocator + ?Sized> Allocator for &A
is a strong hint that&A
can be used interchangeably withA
for purposes of (de)allocation, which would mean thatArc<T, A>
having the heap allocation's layout depend on the layout ofA
is unexpected.
I see. But couldn't the pattern be replaced with a new one? For example, Box<T, A>
could implement a method Box<T, A>::pop_value_with_allocator(Self) -> (T, A)
, which deallocates the pointer, but prior to that it copies the values in order to return them (thoght probably a different method would be required for dynamic sized types as you can't just return them by value).
IMO the existence of
impl<A: Allocator + ?Sized> Allocator for &A
is a strong hint that&A
can be used interchangeably withA
for purposes of (de)allocation, which would mean thatArc<T, A>
having the heap allocation's layout depend on the layout ofA
is unexpected.
Oh, well, I can't ague with opinions. So I don't know that to say about this, even if it's a hint, it's not a demand.
Basically, at the moment
Arc<T, A>
and itsWeak<T, A>
uses:(Something similar happens in
Rc
and itsWeak
, so this whole issue could also be applied to them.)My idea is to move
alloc
fromArc
andWeak
toInnerArc
:This would produce 2 benefits:
1) The size of
Arc
is guaranteed to be the size of a single pointer, which is cool. Also, being more realistic, it means that if you have a non-zero-sized allocator, you don't have to pay the cost of its size on each clone of the instance, instead the allocator is stored in the inner pointer. That means that multiple copies of the same instance won't require multiple copies of the allocator, saving memory unlike the current approach, 2) Doing this also means thatA
doesn't require implementingClone
if you want to clone theArc
, since it's stored in the shared pointer. This would relax the currentArc
cloning implementation, no longer requiringA: Allocator + Clone
but onlyA: Allocator
. It would probably relax the same constrain inmake_mut
,unwrap_or_clone
,downcast
,downcast_unchecked
,From<Vec<T, A>> for Arc<[T], A>
andTryFrom<Arc<[T], A>> for Arc<[T; N], A>
.