-
Notifications
You must be signed in to change notification settings - Fork 9
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
Change AllocRef to take &self #55
Comments
I find the first point in particular very important. I see the second point as a side effect, because data structures that accept How does the last point plays together with #43? |
We remove the existing |
I thought we |
Ah, you're right, I got mixed up. I think we can make this work with specialization, but I'll need to double-check. |
#![feature(specialization)]
trait GlobalAlloc {
fn global_alloc(&self) {}
}
trait AllocRef {
fn alloc_ref(&self) {}
}
impl<A: GlobalAlloc> AllocRef for A {}
struct System;
impl GlobalAlloc for System {}
impl AllocRef for System {}
fn main() {
System.global_alloc();
System.alloc_ref();
} This works, even without the EDIT: Actually this is very nice, as we can "specialize" |
This works because there is a default implementation in the trait, it doesn't forward the from However, this works even with the sound sub-set of specialization. ( #![feature(min_specialization)]
trait GlobalAlloc {
fn global_alloc(&self); // no default implementation in the trait
}
trait AllocRef {
fn alloc_ref(&self); // no default implementation in the trait
}
impl<A: GlobalAlloc> AllocRef for A {
default fn alloc_ref(&self) { A::global_alloc(self) }
}
struct System;
impl GlobalAlloc for System {
fn global_alloc(&self) {}
}
impl AllocRef for System {}
fn main() {
System.global_alloc();
System.alloc_ref();
} |
I think you mixed this up as well, as we call unsafe impl<A: GlobalAlloc> AllocRef for A {
fn alloc(&mut self, layout: Layout, init: AllocInit) -> Result<MemoryBlock, AllocErr> {
// ZST checks etc.
let raw_ptr = match init {
AllocInit::Uninitialized => GlobalAlloc::alloc(self, layout), // Takes &self
AllocInit::Zeroed => GlobalAlloc::alloc_zeroed(self, layout), // Takes &self
};
// error handling
}
// ...
}
Good to know it's working somehow 🙂 |
This is not the most elegant solution, but it's working: struct MyAlloc;
unsafe impl AllocRef for &MyAlloc {
fn alloc(&mut self, _: Layout, _: AllocInit) -> Result<MemoryBlock, AllocErr> {
println!("alloc");
Err(AllocErr)
}
unsafe fn dealloc(&mut self, _: NonNull<u8>, _: Layout) {}
}
struct DataStructure<A> {
alloc: A,
}
impl<A> DataStructure<A>
where
for<'a> &'a A: AllocRef,
{
fn use_alloc(&self) {
(&self.alloc).alloc(Layout::new::<u32>(), AllocInit::Zeroed);
}
}
fn main() {
let s = DataStructure { alloc: MyAlloc };
s.use_alloc();
} |
I remembered a prior discussion about moving away from |
At least it is required to stay at a reference level, it's not necessarily required to stick the I like to summarize the (dis)advantages for
I think both types are more or less equal. For To conclude: We need internal mutability, we only have to decide when it is required:
|
Isn’t this already possibly through I thought that was the point of naming the trait |
Yeah, if it's a reference anyhow, won't we always have access to an owned instance of the reference, as confusing as that sounds? |
I still think that overall ergonomics are better if we change methods to take struct Foo<'a, A>
where
&'a A: AllocRef,
{
alloc: &'a A,
}
impl<'a, A> Foo<'a, A>
where
&'a A: AllocRef,
{
fn alloc_without_mut(&self) {
// Can't mutate the reference, we need to make a mutable copy of it.
let mut a = self.alloc;
a.alloc(Layout::new::<i32>());
}
} The version with struct Bar<A: AllocRef> {
alloc: A,
}
impl<A: AllocRef> Foo<A> {
fn alloc_without_mut(&self) {
self.alloc.alloc(Layout::new::<i32>());
}
} I don't think that the need for inner mutability in allocator implementations is a big issue: in practice every serious allocator will need to use it (even with |
I don't have any objections against this. @TimDiekmann did you previously experiment with a similar API? |
That's the |
|
The allocator trait should not require |
Fair enough, a |
In fact
I disagree: I expect almost all allocators to support |
Okay, perfect then. Most allocators I guess we could then have blanket And most collections would take an |
I personally see little value in I am particularly worried that we are doubling the API complexity for this. |
Yeah, hmm. I can think of allocators that would be easier to implement with |
I think we should ask if there are allocators, which may be slower using |
I would think that allocator implementers could use |
I would expect any allocator that doesn't natively support multi-threading (via thread-local caches) to be |
As @Amanieu pointed out in #55 (comment) I think it's obvious, that we don't want |
@rustbot claim |
This comment has been minimized.
This comment has been minimized.
Changing the alloc() to accept &self instead of &mut self Fixes: [rust-lang#55](rust-lang/wg-allocators#55) This is the first cut. It only makes the change for `alloc` method.
Hi there, I recently came across this issue when I was trying to understand why Bumpalo uses For my use case, a noticeable downside of One thing I didn't quite understand when reading this issue:
What does this look like? What are the benefits/limitations of this approach? I'm still somewhat new to Rust, so I don't quite follow what it means to implement a trait on a reference, or what this bound means. |
@haberman I read through your blog post. There is another significant issue with the It's a bit subtle since it's hidden by lifetime inference but becomes more obvious if you expand it: pub fn alloc<'a, T>(&'a mut self, val: T) -> &'a mut { ... } Because the returned lifetime is tied to a mutable borrow of |
@Amanieu Thank you for reading and for the correction! I received this correction on Twitter also but didn't have a chance to update the article yet. I'm having trouble understanding how AllocRef gets around this problem. Matt explained this to me here but I'm not quite following. |
|
To fulfill this requirement |
This has several advantages:
AllocRef
can now be used by concurrent (lock-free) data structures which only have&self
operations. Currently the allocator would need to be wrapped in a mutex even if the allocator is thread-safe.Sync
. This bound is inherited by data structures using an allocator: a lock-free queue would only beSync
if its allocator isSync
.We can define a blanket implementation ofAllocRef
for all types implementingGlobalAlloc
. This would allow us to change#[global_allocator]
to requireAllocRef
instead ofGlobalAlloc
. This is not possible ifAllocRef
methods take&mut self
.The text was updated successfully, but these errors were encountered: