Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reference consumption and field extraction #146

Open
EpicEric opened this issue Mar 29, 2019 · 0 comments
Open

Reference consumption and field extraction #146

EpicEric opened this issue Mar 29, 2019 · 0 comments

Comments

@EpicEric
Copy link

EpicEric commented Mar 29, 2019

TL;DR: Explanation of the issue with extracting fields from a consumed reference, and initial proposal of a syntax

When handling viewpoint adaptation field extraction (especially with iso origins), there is no way to safely extract multiple let fields, or extracting var fields without reallocating them. Examples:

class Foo
  let x: String iso
  let y: String iso
  new iso create(x': String iso, y': String iso) =>
    x = consume x'; y = consume y'

class Bar
  var x: String iso
  var y: String iso
  new iso create(x': String iso, y': String iso) =>
    x = consume x'; y = consume y'

class Baz
  let arr: Array[String iso]
  new iso create(x': String iso, y': String iso) =>
    arr = [consume x'; consume y']

class Qux
  let tup: (String iso, String iso)  // or "var tup: ..."
  new iso create(x': String iso, y': String iso) =>
    tup = (consume x', consume y')

actor Main
  var _x: String iso = recover String end
  var _y: String iso = recover String end
  new create(env: Env) =>
    let x = "x"; let y = "y"
    let foo: Foo iso = Foo(x.clone(), y.clone())
    let bar: Bar iso = Bar(x.clone(), y.clone())
    let baz: Baz iso = Baz(x.clone(), y.clone())
    let qux: Qux iso = Qux(x.clone(), y.clone())
    
    // Doesn't work, since you can only pick one field
    _x = (consume foo).x
    //_y = (consume foo).y  // "can't use a consumed local in an expression"

    // Works, but only for `var` and requires potentially unnecessary allocations
    _x = bar.x = recover String end; _y = bar.y = recover String end

    // Works, but has runtime issues and requires an error block
    try
      let baz': Baz = consume baz
      _x = baz'.arr.shift()?
      _y = baz'.arr.shift()?
    end

    // Works, but data needs to be in a well-specified tuple
    (_x, _y) = (consume qux).tup

The solution in Qux seems to be the best way to do this currently, but it is still error-prone if the elements are incorrectly rearranged (which is less likely to be the case for fields with different types), and it can get complicated if the receiver needs to read a few fields of a huge tuple.

I specifically mentioned viewpoint adaptation earlier as this should be valid for other reference capabilities, but iso and maybe trn origins would be the most relevant cases.

I would expect something like this to work, but it violates capabilities as expected:

    // Error: iso! is not a subcap of iso
    (_x, _y) = recover
      let foo': Foo ref = consume foo
      (foo'.x, foo'.y)
    end

And consuming from fields directly is not allowed, either:

    // Error: Consume expressions must specify a single identifier
    _x = consume (foo.x); _y = consume (foo.y)

Some sort of syntax that "consumes a reference and returns a tuple of its fields, specified by the programmer" would make sense to respect capabilities. For now, I thought of a "destructor" (as in the reverse of a constructor, or the de-structuring of the origin object) syntax:

    (_x, _y) = destruct (foo' = consume foo) (foo'.x, foo'.y) end

When checking refcaps, if foo is of type Any A, foo' would be Any A^ in both tuple calls.

As far as I can tell, since foo' only lives inside the "destruct" block, no other refcaps could leak, so long as no other values, fields or functions can exist in the rightside (tuple expression) of the block. Alternatively, foo' isn't necessary for purposes other than readability. Some alternative syntaxes:

    (_x, _y) = destruct (_ = consume foo) (_.x, _.y) end
    (_x, _y) = destruct (consume foo) (x, y) end
    (_x, _y) = destruct (consume foo) (.x, .y) end
    (_x, _y) = destruct (consume foo) (_.x, _.y) end
    // etc.

Do note that consume keyword should still be used, as we might not need to consume our original variable when working with refcaps that alias as themselves -- in which case, the syntax is not necessary at all outside of generics.

I'll turn this into a PR if nobody makes a mention about this being unsound, or proposes a better syntax.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants