Ok, I have updated and simplified the DIP38, please take a look. In the proposed 'manual' scheme A, the user annotates each ref argument of a ref-return function with either inref or outref (let's postpone the discussion of what exactly those keywords should be and focus on the logic instead; see bottom of email for a possibility). In proposed scheme B, the inref/outref are instead automatically infered. Let's focus on scheme A to avoid doing interprocedural analysis.
Then all we need is to check whether the program typechecks under the following type conversion rules: global => outref //global: gc-allocated, static, etc. output of ref-return function call => outref outref 'dot' field => outref // field access local => inref global => inref outref => inref temporary => inref where A=>B means that a variable of type A can be used in a context where type B is expected. see DIP38 for details and examples I also would argue that it makes sense for the user to write inref/outref instead of ref, indicating explicit intent on whether or not to escape a ref argument. This is much simpler than rust's named lifetime annotations as far as I understand, since it's just a binary choice. To avoid breaking code, we can assume that 'ref' means 'outref' and 'scope ref' means 'inref'. Note, that this isn't the same as the rejected proposal DIP36, which did not address escaping issues (it only dealt with non ref return functions). Example1: ref T fooa(ref T t) { return t; } ref T bar() { T t; return fooa(t); } currently might compile, but has undefined behavior. Under the new rules (with ref meaning outref), it would not compile because of the illegal conversion local => outref when attempting to call fooa(t). Example2: ref T fooa(inref T t) { static T t2; return t2; } ref T bar() { T t; return fooa(t); } here this compiles because we've annotated the input argument to fooa as inref, as foo returns a global.