www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Placement new and trusted

reply IchorDev <zxinsworld gmail.com> writes:
I thought the 'placement new' feature might replace my need for 
`emplace`, but I don't see a way to mark a placement new as 
` trusted` without marking the call to the object's constructor 
as ` trusted` also?

```d
void main()  safe{
	X x;
	//placement new is  system, so we have to wrap it in  trusted:
	X* xPtr = (()  trusted => new (x) X(500))();
}

struct X{
	void* a;
	this(int a)  system{
		//but we don't want to mark THIS as  trusted!
		this.a = cast(void*)a;
	}
}
```

If anyone has any ideas, please let me know.
Sep 10
next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 11/09/2025 12:29 AM, IchorDev wrote:
 I thought the 'placement new' feature might replace my need for 
 `emplace`, but I don't see a way to mark a placement new as ` trusted` 
 without marking the call to the object's constructor as ` trusted` also?
 
 ```d
 void main()  safe{
      X x;
      //placement new is  system, so we have to wrap it in  trusted:
      X* xPtr = (()  trusted => new (x) X(500))();
 }
 
 struct X{
      void* a;
      this(int a)  system{
          //but we don't want to mark THIS as  trusted!
          this.a = cast(void*)a;
      }
 }
 ```
 
 If anyone has any ideas, please let me know.
Your calling a system function (constructor) from a safe function (main). Placement new doesn't change the rules around this.
Sep 10
parent reply IchorDev <zxinsworld gmail.com> writes:
On Wednesday, 10 September 2025 at 12:41:22 UTC, Richard (Rikki) 
Andrew Cattermole wrote:
 Your calling a  system function (constructor) from a safe 
 function (main).

 Placement new doesn't change the rules around this.
You don't understand: I *WANT* the code *FAIL* to compile because `S`'s constructor is ` system`. But instead, the ` system` constructor gets through because I had to use ` trusted` to get placement new itself to be considered safe. If I have to manually check constructor safety to get around this then I might as well just use `emplace`, which would make this a completely worthless feature.
Sep 10
parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
Ok, gotcha.

Placement new is  system, but you wanted to use it in an  safe function 
iff the constructor to be called is  safe as well.

The reason placement new is  system is because of double-init. It can't 
be a safe operation.
Sep 10
parent reply IchorDev <zxinsworld gmail.com> writes:
On Wednesday, 10 September 2025 at 13:13:34 UTC, Richard (Rikki) 
Andrew Cattermole wrote:
 Placement new is  system, but you wanted to use it in an  safe 
 function iff the constructor to be called is  safe as well.
Pretty much.
 The reason placement new is  system is because of double-init. 
 It can't be a safe operation.
What?! Like, it assigns to the memory twice? What's the point of this feature, then? I thought this could essentially replace `emplace` for constructing types into uninitialised memory, but instead it's just a booby-trap that stops people from making their code ` safe`?
Sep 10
next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 11/09/2025 2:31 AM, IchorDev wrote:
 On Wednesday, 10 September 2025 at 13:13:34 UTC, Richard (Rikki) Andrew 
 Cattermole wrote:
 The reason placement new is  system is because of double-init. It 
 can't be a safe operation.
What?! Like, it assigns to the memory twice? What's the point of this feature, then? I thought this could essentially replace `emplace` for constructing types into uninitialised memory, but instead it's just a booby-trap that stops people from making their code ` safe`?
It does replace emplace, and just like emplace you can double-init memory with it. There is no protection against it apart from it being system (which emplace should also be). The caller is responsible for guaranteeing that the memory passed in is uninitialized, and that behavior is system.
Sep 10
parent reply IchorDev <zxinsworld gmail.com> writes:
On Wednesday, 10 September 2025 at 14:45:52 UTC, Richard (Rikki) 
Andrew Cattermole wrote:
 The caller is responsible for guaranteeing that the memory 
 passed in is uninitialized, and that behavior is  system.
Yeah but I should be able to mark it as ` trusted` without trusting some random bloody constructor as well. Apparently nobody thought to use a syntax that allows marking one but not the other? This is why we need to use the DIP process to add new features like this.
Sep 10
parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 11/09/2025 3:27 AM, IchorDev wrote:
 On Wednesday, 10 September 2025 at 14:45:52 UTC, Richard (Rikki) Andrew 
 Cattermole wrote:
 The caller is responsible for guaranteeing that the memory passed in 
 is uninitialized, and that behavior is  system.
Yeah but I should be able to mark it as ` trusted` without trusting some random bloody constructor as well. Apparently nobody thought to use a syntax that allows marking one but not the other? This is why we need to use the DIP process to add new features like this.
It did go through the DIP process. What has not gone through the DIP process is attributes like localtrusted which would be more akin to what you're asking about.
Sep 10
parent IchorDev <zxinsworld gmail.com> writes:
On Thursday, 11 September 2025 at 03:47:02 UTC, Richard (Rikki) 
Andrew Cattermole wrote:
 It did go through the DIP process.
Blast, you're right. I've even seen the DIP Development post for it before, too! I didn't read into it at the time. I could've probably identified this problem and raised it ahead of time.
Sep 11
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Wednesday, 10 September 2025 at 14:31:27 UTC, IchorDev wrote:
 On Wednesday, 10 September 2025 at 13:13:34 UTC, Richard 
 (Rikki) Andrew Cattermole wrote:
 Placement new is  system, but you wanted to use it in an  safe 
 function iff the constructor to be called is  safe as well.
Pretty much.
 The reason placement new is  system is because of double-init. 
 It can't be a safe operation.
What?! Like, it assigns to the memory twice? What's the point of this feature, then? I thought this could essentially replace `emplace` for constructing types into uninitialised memory, but instead it's just a booby-trap that stops people from making their code ` safe`?
The problem is not really with placement new, it's with constructors. Constructors are allowed to mutate immutable objects (under the assumption that they are initializing a newly-created object). If you call a constructor twice on the same immutable object, which is possible with placement new, it can result in undefined behavior. In fact, it is also possible to trigger UB this way by simply calling the constructor manually: https://github.com/dlang/dmd/issues/20248
Sep 10
parent reply IchorDev <zxinsworld gmail.com> writes:
On Thursday, 11 September 2025 at 04:19:02 UTC, Paul Backus wrote:
 The problem is not really with placement new, it's with 
 constructors. Constructors are allowed to mutate immutable 
 objects (under the assumption that they are initializing a 
 newly-created object). If you call a constructor twice on the 
 same immutable object, which is possible with placement new, it 
 can result in undefined behavior.
But that's the thing: all I want is to construct objects into *freshly-allocated*, *uninitialised memory*; so my desired use-case has a safe interface and can therefore be marked ` trusted`. However the constructor is a wildcard, so I want to leave that part to attribute inference. Do you think it'd be worth submitting an enhancement issue to add something simple like this? ```d new trusted (buffer) S(500); //we trust that the buffer is safe to use, but not S's constructor. ``` The weird placement new syntax means that this looks a bit goofy, but it's better than the feature being essentially dead-on-arrival for the *one thing* I'd ever want it for.
Sep 11
parent Nick Treleaven <nick geany.org> writes:
On Thursday, 11 September 2025 at 08:06:17 UTC, IchorDev wrote:
 But that's the thing: all I want is to construct objects into 
 *freshly-allocated*, *uninitialised memory*; so my desired 
 use-case has a safe interface and can therefore be marked 
 ` trusted`. However the constructor is a wildcard, so I want to 
 leave that part to attribute inference.
 Do you think it'd be worth submitting an enhancement issue to 
 add something simple like this?
 ```d
 new  trusted (buffer) S(500); //we trust that the buffer is 
 safe to use, but not S's constructor.
 ```
No, for 2 reasons: 1. ` trusted` is a function attribute that says the function has a safe interface. Placement new does not have a safe interface, above it depends on how `buffer` is used outside of it and what `S` is. (Yes people often resort to using a trusted function literal without a safe interface, but it's still wrong, and the language shouldn't endorse that). 2. There are various other expressions in the language where you'd need something similar. Phobos uses introspection instead to detect whether to trust an expression or not.
 The weird placement new syntax means that this looks a bit 
 goofy, but it's better than the feature being essentially 
 dead-on-arrival for the *one thing* I'd ever want it for.
It's no different than some other unsafe expressions that Phobos sometimes needs to trust.
Sep 12
prev sibling parent reply Dennis <dkorpel gmail.com> writes:
On Wednesday, 10 September 2025 at 12:29:24 UTC, IchorDev wrote:
 If anyone has any ideas, please let me know.
The trick that's used in druntime is putting the part that still needs to be checked for attributes inside an `if (false)` block, for example: https://github.com/dlang/dmd/blob/c81714b9bc7626e1d235fb2d6279dc73d47c9174/druntime/src/core/lifetime.d#L1981 ```D private T moveImpl(T)(return scope ref T source) { // Properly infer safety from moveEmplaceImpl as the implementation below // might void-initialize pointers in result and hence needs to be trusted if (false) moveEmplaceImpl(source, source); return trustedMoveImpl(source); } ``` Other example: https://github.com/dlang/dmd/blob/c81714b9bc7626e1d235fb2d6279dc73d47c9174/druntime/src/core/internal/newaa.d#L692
Sep 11
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Thursday, 11 September 2025 at 11:47:40 UTC, Dennis wrote:
 On Wednesday, 10 September 2025 at 12:29:24 UTC, IchorDev wrote:
 If anyone has any ideas, please let me know.
The trick that's used in druntime is putting the part that still needs to be checked for attributes inside an `if (false)` block, for example: https://github.com/dlang/dmd/blob/c81714b9bc7626e1d235fb2d6279dc73d47c9174/druntime/src/core/lifetime.d#L1981
You can also use `static if` and a no-op ` system` function: ```d system pure nothrow nogc pragma(inline, true) void inferSystem() {} T* example(T)() { import std.traits: isSafe; static if (!isSafe!(() { new T(); }) inferSystem(); void[] memory = new void[](T.sizeof); return (() trusted => new (memory) T())(); } ```
Sep 11
parent IchorDev <zxinsworld gmail.com> writes:
On Thursday, 11 September 2025 at 17:16:35 UTC, Paul Backus wrote:
 ```d
 void inferSystem()  system pure nothrow  nogc {}

 T* example(T)() {
     static if (!isSafe!(() { new T(); })
         inferSystem();
     return (()  trusted => new (new void[](T.sizeof)) T())();
 }
 ```
Unfortunately that would give you an incredibly unhelpful error message.
Sep 12
prev sibling parent IchorDev <zxinsworld gmail.com> writes:
On Thursday, 11 September 2025 at 11:47:40 UTC, Dennis wrote:
 The trick that's used in druntime is putting the part that 
 still needs to be checked for attributes inside an `if (false)` 
 block, for example:

 ```d
 private T moveImpl(T)(return scope ref T source)
 {
     // Properly infer safety from moveEmplaceImpl as the 
 implementation below
     // might void-initialize pointers in result and hence needs 
 to be  trusted
     if (false) moveEmplaceImpl(source, source);

     return trustedMoveImpl(source);
 }
 ```
Well, that would work. Thank you for pointing it out. It's probably better to do it like this to avoid the potential for an unconditional cache miss (depending on how smart the compiler feels today): ```d private T moveImpl(T)(return scope ref T source){ if(true) return trustedMoveImpl(source); moveEmplaceImpl(source, source); assert(0); } ``` It would still be nice to have a proper, intuitive way of doing this though. Hacks are great and all, but they are still hacks. One time I got my code to call into Swift code by writing inline assembly to match Swift's calling convention; but that was a hack, and I'm still excited that LDC has recently gained some degree of native Swift interoperability.
Sep 12