www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Transferring 32 bits

reply Salih Dincer <salihdb hotmail.com> writes:
Hi All,

Transferring 32 bits (4 characters) of data from a data source, 
for example to the S structure takes place...

Everything is fine with the constructor but I can't keep the 
object alive with the foo() convenience function! My sample code 
is like this:

```d
import std.stdio;

struct S
{
   char[size] bytes;

   alias toString this;
   string toString() const
   {
     scope res = [ cast(string) bytes ];
     return res[0];
   }
}

string foo(char[size] bytes)
{
   scope res = S(bytes);
   return res.toString(); // problem here
}

enum size = 4;
void main()
{
   char[size] text = "abcd".dup;

   auto m = S(text);  // test ok
   assert(m == text);

   foo(text).writefln!"(%s)"; // (¿)
}
```

SDB 79
Aug 25 2022
next sibling parent reply Salih Dincer <salihdb hotmail.com> writes:
On Thursday, 25 August 2022 at 12:18:19 UTC, Salih Dincer wrote:
 Everything is fine with the constructor but I can't keep the 
 object alive with the foo() convenience function!
```d string foo(char[size] bytes) { scope res = new S(bytes); return res.toString(); // problem here } ``` **One more thing:** It's ok when I construct S with the new operator! SDB 79
Aug 25 2022
parent Adam D Ruppe <destructionator gmail.com> writes:
On Thursday, 25 August 2022 at 12:22:56 UTC, Salih Dincer wrote:
   scope res = new S(bytes);
The `scope` keyword means you promise not to let any references escape this scope. Your toString does do that, so the compiler might optimize this `new` away; the scope and new keywords here cancel each other out, leaving you with the same problem.
 It's ok when I construct S with the new operator!
Still wrong code, but it'd work here because the bytes then are GC managed instead of stack temporaries so you still cast to immutable but at least it isn't use-after-free.
Aug 25 2022
prev sibling parent reply Adam D Ruppe <destructionator gmail.com> writes:
On Thursday, 25 August 2022 at 12:18:19 UTC, Salih Dincer wrote:
   char[size] bytes;

   alias toString this;
   string toString() const
   {
     scope res = [ cast(string) bytes ];
     return res[0];
   }
This code is completely wrong. You're improperly casting mutable and limited lifetime data to string, then returning it. And that scope res has no reason to exist; the code is identical to just `return cast(string) bytes;` which the compiler will slice for you - taking a pointer of it - then you return that slice with a new type. So this is returning a pointer to a local variable which is liable to cease to exist as soon as the function calling it returns...
 string foo(char[size] bytes)
 {
   scope res = S(bytes);
   return res.toString(); // problem here
 }
...which is exactly what you're doing here. Those bytes inside S live on the stack of this foo function. You return a pointer to them, then the foo function returns and those bytes on the stack cease to exist (actually reused memory but same thing conceptually) yet you keep the reference.
   auto m = S(text);  // test ok
   assert(m == text);
this only works because m still exists at the point of the assert, once there's another function involved the stack memory is gone. What you want to do instead is either `.idup` the bytes or make your toString do a `void delegate(in char[]) sink` and you pass the bytes to the sink casting to char[].
Aug 25 2022
next sibling parent Paul Backus <snarwin gmail.com> writes:
On Thursday, 25 August 2022 at 12:40:57 UTC, Adam D Ruppe wrote:
 What you want to do instead is either `.idup` the bytes or make 
 your toString do a `void delegate(in char[]) sink` and you pass 
 the bytes to the sink casting to char[].
Rather than using a delegate sink, I believe the current recommended approach [1] is to make toString a template that accepts an output range: import std.range; void toString(Sink)(ref Sink sink) if (isOutputRange!(Sink, char)) { put(sink, cast(char[]) bytes); } [1] https://dlang.org/phobos/std_format_write.html
Aug 25 2022
prev sibling parent Salih Dincer <salihdb hotmail.com> writes:
On Thursday, 25 August 2022 at 12:40:57 UTC, Adam D Ruppe wrote:
 This code is completely wrong. You're improperly casting 
 mutable and limited lifetime data to string, then returning it. 
 And that scope res has no reason to exist; the code is 
 identical to just `return cast(string) bytes;` which the 
 compiler will slice for you  - taking a pointer of it - then 
 you return that slice with a new type.
Yes, but the code works withthe new operator. I just wanted to go to the limits D allowed; to get some understanding of reference types and scope... On Thursday, 25 August 2022 at 14:17:16 UTC, Paul Backus wrote:
 ...to make toString a template that accepts an output range:
 
 ```d
     import std.range;

     void toString(Sink)(ref Sink sink)
     if (isOutputRange!(Sink, char))
     {
         put(sink, cast(char[]) bytes);
     }
 ```
I tried using outputrange but without success. But I implemented like this and was able to return a living string perfectly: ```d import std.format, std.stdio; struct S(int len) { char[len] bytes; void toString(void delegate(const(char)[]) sink) const { auto res = cast(char[]) bytes; typeid(res).writeln(" toString()=>"); sink.formattedWrite("%s", res); } } char[] foo(char[9] bytes) { auto res = cast(char[]) format("%s", S!9(bytes)); scope(exit) typeid(res).writeln(" foo()=>"); return res; } void main() { char[9] text = " Hello D".dup; auto test = S!9(text); test.writeln; // ok foo(text).writeln; // ok } /* Output: char[] toString()=> Hello D char[] toString()=> char[] foo()=> Hello D */ ``` Thank you all, it was helpful... SDB 79
Aug 25 2022