-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Amend 0809 to reduce the number of traits. #1401
Conversation
Implementation: rust-lang/rust@master...Stebalien:simpler-place |
I do not have any immediate problem with this amendment. I wll note that another acceptable variant might be to make the |
What's the closest approximation of the non-existent plan for DST support? For sized objects, it makes sense to make However, if pointer can point to a DST, it makes more sense to make Does this make sense? |
Use the following trait structure: unsafe trait Placer<Data: ?Sized> { type Place: Place<Data>; fn make_place(&mut self) -> Self::Place; } unsafe trait Boxer<Data: ?Sized>: Sized { type Place: Place<Data, Owner=Self>; fn make_place() -> Self::Place; } trait Place<Data: ?Sized> { type Owner; fn pointer(&mut self) -> *mut Data; unsafe fn finalize(self) -> Self::Owner; } The key differences are: 1. The `Placer` and `Boxer` traits must be unsafe because the compiler will blindly write to the pointers returned by `Placer::make_place(self).pointer()` and `Boxer::make_place().pointer()`. Note: we could have made `Place` itself unsafe but, if we ever decide to eventually support DST placement new, the `DstBoxer` and `DstPlacer` traits would need to be unsafe so there's no point in making Place unsafe. 2. Merge `InPlace` into `Place`. 3. Make the boxing side of the hierarchy mirror the "in (PLACE)" side of hierarchy.
Can we do trait Boxer<Data: ?Sized>: Sized + Placer
where <Self as Placer>::Place: Place<Data, Owner=Self> { } With the corresponding blanket impl? |
No, |
Ah, I didn't notice the presence/lack of that arguments, whoops. Well I suppose with allocators, now boxers would take an argument---the allocator, so maybe they could be unified after all, but that can be dealt with separately. |
Inspired by the allocator RFC, add a
|
I have been assuming that if you needed to pass a concrete allocator value in, then you would use the placement-in form, even if the final type is a Admittedly, such an approach looked somewhat better (IMO) when it was written as let handle = in arena { boxed_value }; rather than the newer: let handle = arena <- boxed_value; Anyway, my point is really just that the main use case for |
(this cross-cuts lang and libs, i think, so i am tagging with both, but i hope we can resolve at lang team mtg) |
@pnkfelix Maybe add a default bound? trait Boxer<Data: ?Sized>: Sized + Placer + Default
where <Self as Placer>::Place: Place<Data, Owner=Self> { } So there is still only want trait to worry about? Basically my concern is every boxer can be a placer too, so it would be nice to enforce that relationship without adding much of a burden. |
I don't understand this claim. Are you anticipating code that is generic over all Placers, where a client would want to pass in a boxer? That is the only interpretation that I can work out of the claim above. (In which case I guess one would have to pass in a sentinel representative of the Boxer, just to get the macro expansion to work out. Implementing |
There's a bit of a problem with both this and the original trait hierarchies. The let a: Box<_> = HEAP <- 1; // Owner = Box<T>
let b: Rc<_> = HEAP <- 1; // Owner = Rc<T> |
Ah, @pnkfelix I missed that Boxer would be implemented by Box, Rc, etc themselves, rather than e.g. HEAP. This almost seems good, except type inference is still confused. pub mod protocol {
use std::default::Default;
trait Placer<Data: ?Sized> {
type Place: Place<Data>;
fn make_place(&mut self) -> Self::Place;
}
trait Boxer<Data: ?Sized>: Sized
where <Self::Placer as Placer<Data>>::Place: Place<Data, Owner=Self>
{
type Placer: Placer<Data> + Default;
}
fn box_make_place<D, B: Boxer<D>>() -> <B::Placer as Placer<D>>::Place
where <B::Placer as Placer<D>>::Place: Place<D, Owner=B>
{
let mut p: B::Placer = Default::default();
p.make_place()
}
unsafe trait Place<Data: ?Sized> {
type Owner;
fn pointer(&mut self) -> *mut Data;
unsafe fn finalize(self) -> Self::Owner;
}
}
macro_rules! box_ {
($value:expr) => { {
let mut place = ::protocol::box_make_place();
let raw_place = ::protocol::Place::pointer(&mut place);
let value = $value;
unsafe {
::std::ptr::write(raw_place, value);
::protocol::Place::finalize(place)
}
} }
}
fn main() {
let b: Box<usize> = box_!(1);
} |
@Stebalien It sounds like you are anticipating some future use case where you want to (simultaneously) pass in a placer (a la Am I interpreting that correctly? I am not sure there exists a good way today to encode such flexibility. (Digression that risks derailing the comment thread of this amendment PR follows) E.g. this won't work: pub trait InPlace<Data> where Data: ?Sized {
fn pointer(&mut self) -> *mut Data;
unsafe fn finalize<Owner>(self) -> Owner;
} (because there's no way to provide an implementation of that We could do something like this: pub trait InPlace<Data> where Data: ?Sized {
fn pointer(&mut self) -> *mut Data;
unsafe fn finalize<O:Owner>(self) -> O;
}
trait Owner {
...
} but then we'd have to design a generic |
yes, type inference is what continues to block us landing the |
For @Stebalien 's latest concern, here is a variation on my last one. pub mod protocol {
use std::default::Default;
trait Placer<Data: ?Sized, Owner> {
type Place: Place<Data, Owner=Owner>;
fn make_place(&mut self) -> Self::Place;
}
trait Boxer<Data: ?Sized>: Sized
{
type Placer: Placer<Data, Self> + Default;
}
fn box_make_place<D, B>() -> <B::Placer as Placer<D, B>>::Place
where B: Boxer<D>
{
let mut p: B::Placer = Default::default();
p.make_place()
}
unsafe trait Place<Data: ?Sized> {
type Owner;
fn pointer(&mut self) -> *mut Data;
unsafe fn finalize(self) -> Self::Owner;
}
}
macro_rules! box_ {
($value:expr) => { {
let mut place = ::protocol::box_make_place();
let raw_place = ::protocol::Place::pointer(&mut place);
let value = $value;
unsafe {
::std::ptr::write(raw_place, value);
::protocol::Place::finalize(place)
}
} }
}
fn main() {
let b: Box<usize> = box_!(1);
} |
@pnkfelix I have an idea of using "phantom args" to aid dispatch. Will try to write up. |
Ok, successfully get "trait not implemented" rather than "not enough information" error with this. pub mod protocol {
use std::default::Default;
use std::marker::PhantomData;
trait Placer<Data: ?Sized, Owner> {
type Place: Place<Data, Owner=Owner>;
fn make_place(&mut self) -> Self::Place;
}
trait Boxer<Data: ?Sized>: Sized
{
type Placer: Placer<Data, Self> + Default;
}
fn box_make_place<D, B>(_: PhantomData<B>)
-> <B::Placer as Placer<D, B>>::Place
where B: Boxer<D>
{
let mut p: B::Placer = Default::default();
p.make_place()
}
unsafe trait Place<Data: ?Sized> {
type Owner;
fn pointer(&mut self) -> *mut Data;
unsafe fn finalize(self) -> Self::Owner;
}
type BoxerHelp<T> = (T, PhantomData<T>);
}
macro_rules! box_ {
($value:expr) => { {
let (a, b): ::protocol::BoxerHelp<_>;
b = ::std::marker::PhantomData;
let mut place = ::protocol::box_make_place(b);
let raw_place = ::protocol::Place::pointer(&mut place);
let value = $value;
unsafe {
::std::ptr::write(raw_place, value);
a = ::protocol::Place::finalize(place)
}
a
} }
}
fn main() {
let b: Box<usize> = box_!(1);
} |
I'm trying to use a custom allocator (in this case, a not so custom HEAP) for both @Ericson2314, that may work although I'm not sure why |
@Ericson2314 Could you check if your scheme solves the test case from rust-lang/rust#27292? I guess it might be a different way of solving the problem I solved by having a single (safe) method for writing the value and finalizing the placer, but not the main issue (related to DSTs). |
@Stebalien I'll concede that once I correctly understood @eddyb In the PR description right? working on it. |
I don't know yet how we'll do it. But at least now I understand what you are trying to do. :) One obvious and perhaps ugly hack is to not plug your allocator directly in as the LHS of struct BoxIn<A:Allocator>(A);
struct RcIn<A:Allocator>(A);
let a = BoxIn(alloc) <- EXPR;
let b = RcIn(alloc) <- EXPR;
impl<A> Placer for BoxIn<A> { ... }
impl<A> Placer for RcIn<A> { ... } |
Ok, here is both appendices modified to my proposal. @eddyb It doesn't work for DSTs. My diagnosis is the compiler is confused because it can't rule out there isn't no, e.g. @Stebalien this includes HEAP working with Box and Rc without newtype wrappers too. |
One last benefit of associating Boxer with a placer is moving HEAP out of liballoc (for motivation see #1398 (comment)). The old way, it must be there for coherence to implement Boxer. But with the new way, Boxer can be implement for all allocators (assuming allocator->placer blanket impl), in liballoc, and HEAP defined downstream. edit missing word. |
@Ericson2314, a
Sorry, I don't understand this statement.
You can't define a blanket impl of |
You are right :): I forgot the Default bound in my previous post, oops [it's now editted]. I assume you also forgot because you worked from mine. But don't forget the None of these error are inescapable. Your second impl fixed looks like: impl<A: Allocator + Default, T> Boxer<T> for Box<A, T> {
type Place = IntermediateBox<A, T>;
fn make_place() -> Self::Place {
<A as Default>::default().make_place()
}
} Which indeed shows my changes to I guess the question is when would one write a Boxer instance that doesn't look like this (as in using a default allocator)? |
and
Boxer isn't supposed to take a custom allocator anyways (edited).
Added (defaults to |
impl Boxer<[T]> for Vec<T> {}
let v: Vec<_> = box [1, 2, 3]; |
Also, I'd expect the |
Masterstroke :). If the placer for Vec was by-value, this would actually fall into my pattern with Vec's existing
Hmm, I don't think system allocators let on do that. I think of HEAP as a handle to the global heap, and in general placers should be handles (e.g.
I prefer my fixed version of your counter-example, because I'd still like any allocator implementing default to get |
@Ericson2314 I meant the testcase added in the PR, with the safe and inference-friendly API. Also, memcpy optimization is ensured by abusing |
@eddyb, this would also allow |
@eddyb No luck. https://gist.github.com/Ericson2314/d6e3f9c4c37eb2d4a19b first revision just gets macro definition in function, second changes some I tried adding |
Mm, we'd need to magically add those attributes to tl;dr: I rather live with the unsafely or |
@eddyb in my tests, the only thing that affects RVO is inlining, not use std::{mem, ptr};
struct Thing([usize; 256]);
impl Thing {
#[inline(never)]
fn new() -> Self {
Thing([0usize; 256])
}
// #[inline(never)]
fn pointer(&mut self) -> *mut [usize; 256] {
&mut self.0 as *mut _
}
}
#[inline(never)]
fn new_inner() -> [usize; 256] {
let mut out = [0usize; 256];
for (i, v) in out.iter_mut().enumerate() {
*v = i;
}
out
}
fn main() {
let mut thing = Thing::new();
let ptr = thing.pointer();
unsafe {
ptr::write(ptr, new_inner());
}
} |
@Stebalien I was seeing |
Do you have a test case I can play with?Steven Allen |
Well, this might explain why I can't seem to reproduce with just my prototype testcase: declare noalias i8* @__rust_allocate(i64, i64) unnamed_addr #1
...
%0 = tail call i8* @__rust_allocate(i64 20, i64 4) #1, !noalias !0 So it seems we've started putting noalias metadata on the allocator functions, and after inlining everything is nicely optimized.. With Well, isn't it wonderful when changes somewhere else break your "solution"? Making Or... not? Even |
I'm mostly wondering if we can just transmute to |
@Stebalien as opposed to |
@eddyb Either way. My point is, does it really matter what |
@Stebalien at this point, my original testcase doesn't show differences between |
@Ericson2314, I have replacement (full) RFC draft that includes many of your suggestions (and other changes that have come out of discussions here and elsewhere) here. I'll probably submit a pull request in a few days |
@Stebalien I still prefer my safe desugaring, although it technically can be built on top of the main traits. |
@Stebalien No, I mean the safe |
Sorry, I wish GitHub would show usernames in emails instead of names... How does that interact with However, I considered using the following safe method but wasn't sure how well it would optimize: trait Place<Data> {
type Owner;
fn emplace(self, data: Data) -> Self::Owner;
} Where the implementer would be responsible for emplacing the data. The downside is that this gives implementers the ability to shoot themselves in the foot by allocating the place inside |
@Stebalien I just have it a default method next to |
So something like: fn emplace<D, P: Place<D>>(place: P, data: D) -> P::Owner {
unsafe {
ptr::write(place.pointer(), value);
place.finalize()
}
} The one problem with this is that |
Just realized that a MIR desugaring wouldn't be affected by the |
Incorporated feedback and moved into a new RFC (#1426). It was growing a bit large for a patch. |
Use the following trait structure:
The key differences are:
Place
trait must be unsafe because the compiler willblindly write to the pointer returned by
Place::pointer
.InPlace
intoPlace
.side of hierarchy.
Note: I'm submitting this as an amendment because it only modifies an appendix. I'd be happy to submit a separate RFC if wanted.