Jump to page: 1 2
Thread overview
Returning a reference to be manipulated
Apr 13, 2023
Chris Katko
Apr 13, 2023
IchorDev
Apr 13, 2023
Paul Backus
Apr 13, 2023
Jacob Shtokolov
Apr 14, 2023
kdevel
Apr 14, 2023
Bastiaan Veelo
Apr 14, 2023
kdevel
Apr 14, 2023
Dennis
Apr 15, 2023
kdevel
Apr 15, 2023
kdevel
Apr 15, 2023
kdevel
Apr 15, 2023
Dennis
Apr 15, 2023
Dennis
Apr 15, 2023
kdevel
Apr 15, 2023
Dennis
Apr 15, 2023
kdevel
Apr 16, 2023
Dennis
April 13, 2023

I'm trying to figure out how to return a reference to something that may not be a reference type.

struct stats
{
float[string] data=0;

float ref opIndex(string key)
  {
  return data[key]; // want a ref to a specific element
  }
}

void test()
{
stats foo;
auto x = foo.bar(); // returns some sort of reference
       // data["tacos"] = 0
x++;   // data["tacos"] = 1
}

Right now, I'm using pointers which resolves to:

// float* opIndex(string key){...} using pointer
(*s["tacos"])++; // works with pointer, but is strange looking

s["tacos"]++; // preferred syntax or something similar
April 13, 2023

On Thursday, 13 April 2023 at 07:05:10 UTC, Chris Katko wrote:

>

I'm trying to figure out how to return a reference to something that may not be a reference type.

struct stats
{
float[string] data=0;

float ref opIndex(string key)
  {
  return data[key]; // want a ref to a specific element
  }
}

void test()
{
stats foo;
auto x = foo.bar(); // returns some sort of reference
       // data["tacos"] = 0
x++;   // data["tacos"] = 1
}

Right now, I'm using pointers which resolves to:

// float* opIndex(string key){...} using pointer
(*s["tacos"])++; // works with pointer, but is strange looking

s["tacos"]++; // preferred syntax or something similar

I'm pretty sure you can't return ref to a value-type without it being a pointer, or a slice.
Unlike C++, D does not have ref variables (&). You can have value-type function parameters that are ref though.
But I feel that the easier solution for the example you gave would be to simply save the string used to index the float, rather than a reference to the float itself:

import std.stdio: writeln;

struct Stats{
	float[string] data;
	
	float opIndex(string key){ return data[key]; }
	float opIndexAssign(float val, string key){ data[key] = val; return val; }
}

void main(){
	Stats foo;
	foo.data = ["tacos": 0];
	
	string ind = "tacos";
	float x = foo[ind];
	writeln(foo.data[ind]); //0
	x++;
	foo[ind] = x;
	writeln(foo.data[ind]); //1
}

Passing pointers around might be faster, but it's error-prone, and cumbersome for simple operators as you mentioned. However, you could always wrap your pointer in a struct with some necessary operator overloads:

import std.stdio: writeln;

struct Float{
	float* data;
	
	float opUnary(string op: "++")(){ (*data)++; return *data; }
}

struct Stats{
	float[string] data;
	
	Float opIndex(string key){ return Float(key in data); }
}

void main(){
	Stats foo;
	foo.data = ["tacos": 0];
	
	Float x = foo["tacos"];
	writeln(foo.data["tacos"]); //0
	x++;
	writeln(foo.data["tacos"]); //1
}
April 13, 2023

On Thursday, 13 April 2023 at 07:05:10 UTC, Chris Katko wrote:

>

Right now, I'm using pointers which resolves to:

// float* opIndex(string key){...} using pointer
(*s["tacos"])++; // works with pointer, but is strange looking

s["tacos"]++; // preferred syntax or something similar

You can use a wrapper struct with alias this to make the pointer a little nicer to use:

struct Ref(T)
{
    T* ptr;
    ref inout(T) deref() inout { return *ptr; }
    alias deref this;
}

Ref!T byRef(T)(ref T obj)
{
    return Ref!T(&obj);
}

struct stats
{
    float[string] data;

    Ref!float opIndex(string key)
    {
        return data.require(key, 0).byRef;
    }
}

void main()
{
    stats foo;
    auto x = foo["tacos"];
    x++;
    assert(foo["tacos"] == 1);
}
April 13, 2023

On Thursday, 13 April 2023 at 07:05:10 UTC, Chris Katko wrote:

>

I'm trying to figure out how to return a reference to something that may not be a reference type.

@safe:

struct Stats
{
    float[string] data;

    ref opIndex(string key) return
    {
        // The `require()` takes care of non-existing values, initializing them
        return data.require(key, 0);
    }
}

void main()
{
    import std.stdio;

    Stats foo;

    // 1. You can't "capture" a reference, only use it directly
    foo["tacos"] += 2;

    // 2. You can use an alternative function-calling form (sometimes replaced by the `with` operator)
    ((ref float val) => val += 2)(foo["burritos"]);

    // 3. As a last resort, use pointers or make struct wrappers around them
    auto x = &foo["quesadillas"];
    *x += 2;

    writeln(foo);
}
April 14, 2023

On Thursday, 13 April 2023 at 22:09:48 UTC, Jacob Shtokolov wrote:

>

[...]
ref opIndex(string key) return
[...]

Regarding the return ref I found this code of 2019 on my harddisk which is from or was motivated by a dconf talk:

ref int foo (ref int i)
{
   return i;
}

ref int bar ()
{
   int i;
   return foo (i);
}

void main ()
{
   import std.stdio;
   auto i = bar;
   i.writeln;
}

Up to dmd v2.100.2 I am warned/get an error during compilation:

$ dmd returnref2.d
returnref2.d(3): Deprecation: returning `i` escapes a reference to parameter `i`
returnref2.d(1):        perhaps annotate the parameter with `return`
$ dmd -dip1000 returnref2.d
returnref2.d(3): Error: returning `i` escapes a reference to parameter `i`
returnref2.d(1):        perhaps annotate the parameter with `return`

With later dmd versions (up to including v2.102.2) the code compiles without complaints. Is this intended?

April 14, 2023

On Friday, 14 April 2023 at 00:50:31 UTC, kdevel wrote:

>
ref int foo (ref int i)
{
   return i;
}

ref int bar ()
{
   int i;
   return foo (i);
}

void main ()
{
   import std.stdio;
   auto i = bar;
   i.writeln;
}

Up to dmd v2.100.2 I am warned/get an error during compilation:

$ dmd returnref2.d
returnref2.d(3): Deprecation: returning `i` escapes a reference to parameter `i`
returnref2.d(1):        perhaps annotate the parameter with `return`
$ dmd -dip1000 returnref2.d
returnref2.d(3): Error: returning `i` escapes a reference to parameter `i`
returnref2.d(1):        perhaps annotate the parameter with `return`

With later dmd versions (up to including v2.102.2) the code compiles without complaints. Is this intended?

I think this is intended. Adding @safe: on top makes the complaint come back (in dmd 2.102 it is deprecated, in 2.103 it is an error).

-- Bastiaan.

April 14, 2023

On Friday, 14 April 2023 at 09:42:14 UTC, Bastiaan Veelo wrote:

> >

[...]
Up to dmd v2.100.2 I am warned/get an error during compilation:

$ dmd returnref2.d
returnref2.d(3): Deprecation: returning `i` escapes a reference to parameter `i`
returnref2.d(1):        perhaps annotate the parameter with `return`
$ dmd -dip1000 returnref2.d
returnref2.d(3): Error: returning `i` escapes a reference to parameter `i`
returnref2.d(1):        perhaps annotate the parameter with `return`

With later dmd versions (up to including v2.102.2) the code compiles without complaints. Is this intended?

I think this is intended. Adding @safe: on top makes the complaint come back (in dmd 2.102 it is deprecated, in 2.103 it is an error).

What irritates me is that only with return ref int

ref int foo (return ref int i)
{
   return i;
}
[...]

dmd complains:

returnref2.d(9): Error: returning `foo(i)` escapes a reference to local variable `i`

The documentation [1] says that a ref parameter may not be returned unless it is return ref. But in fact it is returned unless it is return ref. Probably i get/got the meaning of the English modal verb "may" wrong.

The documentation also says that return ref parameters are used to ensure that the returned reference will not outlive the matching argument's lifetime. I don't get it! Is there any legitimate use of returning a ref such that it outlives the matching argument's lifetime? If not: Isn't this return ref completely redundant?

[1] https://dlang.org/spec/function.html

April 14, 2023

On Friday, 14 April 2023 at 10:31:58 UTC, kdevel wrote:

>

But in fact it is returned unless it is return ref.

When using return ref, return scope, scope etc., you should be using the latest compiler and annotate functions you want checked with @safe. In previous versions, the compiler would often conflate return ref and return scope, and it was also inconsistent in whether it would do checks in @safe, @system, and even 'default/unannotated' functions.

Now, it is more consistent, performing checks in @safe code only.

>

I don't get it! Is there any legitimate use of returning a ref such that it outlives the matching argument's lifetime? If not: Isn't this return ref completely redundant?

The annotation is needed because the compiler can't always figure out what you're doing with a ref parameter:

ref int mysteryFunc(ref int x) @safe; // external implementation

ref int escape() @safe
{
    int local; // allocated on stack frame, should not escape this function
    return mysteryFunc(local); // is this safe?
}

Is this indeed @safe? It is, provided that mysteryFunc doesn't return its parameter x. It can be implemented like this for example:

ref int mysteryFunc(ref int x) @safe
{
    x++;
    return *(new int);
}

But it wouldn't be safe if x were returned, so the compiler must know about that when it happens, hence return ref:

ref int mysteryFunc(return ref int x) @safe
{
    return x;
}

Now the compiler can catch that return mysteryFunc(local) is unsafe. Note that if mysteryFunc is a template function, nested function, or returns auto, then the compiler infers attributes automatically, including return ref. Then you can still write it as mysteryFunc(ref int x) and it will automatically be treated as return ref.

April 15, 2023

On Friday, 14 April 2023 at 11:18:21 UTC, Dennis wrote:

>

On Friday, 14 April 2023 at 10:31:58 UTC, kdevel wrote:

>

But in fact it is returned unless it is return ref.

When using return ref, return scope, scope etc., you should be using the latest compiler and annotate functions you want checked with @safe.

We are writing at cross puroposes. I am not asking how I could help the compiler. My point is: I have code from 2019 which produced a deprecation warning

returnref2.d(3): Deprecation: returning i escapes a reference to parameter i, perhaps annotate with return

when compiled with with the compiler of that time (v2.093.1). When I use a recent compiler (v2.102.2) the code compiles without any complaints. I am asking myself what has deprecacted? I mean: Deprecation notes are built into the compiler in order to enable a "soft landing" for a scheduled breaking change. But I cannot see any breaking change at all regarding the code example in

https://forum.dlang.org/post/iysfuhjwyalnnmalbdrh@forum.dlang.org

April 16, 2023
It was bad analysis by the compiler, which has since been corrected.

It should have been applied only to @safe code.
« First   ‹ Prev
1 2