ponylang

different capability for class with constructor


What is the explanation that instances of Data1 and Data2 have different capabilities?

The line where an object of Data1 class is instantiated compiles without a problem, but the line with Data2 get's the error saying that "right side must be a subtype of left side".

class Data1 
  let _x: U8 = 0

class Data2
  let _x: U8
  new create() => _x = 0

actor Main
  new create(env: Env) =>
    let d1: Data1 iso = Data1
    let d2: Data2 iso = Data2

Solution

  • In Pony, there are many places where you can omit basic elements of syntax or structure, and expect them to be filled in with implicit defaults. The answer to your question here about the difference between Data1 and Data2 has to do with two examples of implicit defaults, which happen to not have the same capability.

    The Data2 class has a single constructor, new create() => _x = 0, which has a implicit default receiver capability of ref. That is, it implicitly expands to new ref create() => _x = 0.

    The Data1 class has no constructor, so Pony creates an implicit default constructor for you, which is new iso create(). The _x = 0 from your field declaration also gets implicitly transferred to the body of the constructor, but that's somewhat outside the scope of your question.


    So, in this case, assigning let d1: Data1 iso = Data1, since the created object will be of type Data1 iso^, which can be assigned to Data1 iso. Assigning let d2: Data2 iso = Data2 doesn't work, since the created object will be of type Data2 ref^, which can't be assigned to Data2 iso without breaking the isolation guarantees.

    Changing your Data2 constructor to new iso create() is the best solution for making your example code work. We don't use iso as the implicit default capability for constructors because it would put additional constraints on the parameters to the constructor (they would all have to be sendable).


    For completeness, note that there is another way around your problem, on the caller side. If you place the constructor call in a recover block, you can "lift" to a capability that has stronger guarantees (for example, from ref to iso). This works because the recover block enforces other constraints on object references used within it (for example, any references passing into the recover block must be sendable), that will uphold the guarantees you are lifting to. This assignment would look like:

        let d2: Data2 iso = recover Data2 end