http://d.puremagic.com/issues/show_bug.cgi?id=9238
--- Comment #13 from Andrei Alexandrescu <[email protected]> 2013-01-09 18:32:23 PST --- Design #2: ref return is sealed by arguments ============================================ So design #1 has the obvious issue that call chains can't propagate ref returns upwards even when it's safe to do so. To improve on that, let's devise a refined rule: ---------- For functions returning ref, the lifetime of the returned object spans at least the lifetime of its shortest-lived argument. ---------- Impact on desiderata: Reconsidering the troublesome example: ref int identityImpl(ref int a) { return a; } ref int identity(ref int a) { return identityImpl(a); } When compiling identity(), the compiler (without seeing the body of identityImpl) figures that the lifetime of the value returned by identityImpl(a) is at least as long as the lifetime of a itself. Therefore identity() typechecks because it is allowed to return a proper. Safety is still guaranteed however. This is because a function can never escape a reference to an object of shorter lifetime than the lifetime of the reference. Reconsidering the front() example: struct Range { private AnotherRange _source; // ... inside some Range implementation ... ref T front() { return _source.front; } // fine } front() compiles because front is really a regular function taking a "ref Range this". Then _source is scoped inside "this" so from a lifetime standpoint "this", _source, and the result are in good order. ref int fun() { Range r; return r.front; // error } fun() does not compile because the call r.front returns a value with the lifetime of r, so returning a ref is tantamount to escaping the address of a local. ref int gun(Range r) { return r.front; // error } This also doesn't compile because the result of r.front has the lifetime of r, which is passed by value into gun. ref int gun(ref Range r) { return r.front; // fine } This does work because the result has the same lifetime as r. The question remains on how to handle rvalues bound to ref parameters. The previous design required that rvalues live as long as the scope, and this design would allow that too. But this design also allows the C++-style destruction of rvalues: in the call foo(bar()), if foo returns a ref, it must be used immediately because bar will be destroyed at the end of the full expression. If we want to keep the current D rule of destroying rvalue parameters right after the call to the function, that effectively disallows any use of the ref result. This may actually be a meaningful choice. The largest problem of this design is lifetime pollution. Consider the ScopedContainer example: ref T opIndex(size_t n) { return payload_[n]; } In the call c[42], the shortest lifetime is actually that of n, which binds to the rvalue 42. So the compiler is forced to a shorter guarantee of the result lifetime than the actual lifetime, because of an unrelated parameter. Summary ======= 1. Design is safe 2. Design allows binding rvalues to ref parameters. For usability, temporaries must last at least as long as the current expression (C++ style). 3. Returning ref parameters works with fewer restrictions than the previous design. 4. Sealed containers are implementable. 5. Difficulty is moderate on the implementation side and moderate on the user side. Next iteration of the design will attempt to refine the lifetime of results so as to avoid pollution. -- Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email ------- You are receiving this mail because: -------
