Return an opaque object to the caller without violating type-safety

function declares an opaque return type swiftui
associatedtype
swift return generic type
swift type erasure
opaque types
opaque type rust
swift return protocol with associated type
swift return type

I have a method which should return a snapshot of the current state, and another method which restores that state.

public class MachineModel
{
    public Snapshot CurrentSnapshot { get; }
    public void RestoreSnapshot (Snapshot saved) { /* etc */ };
}

The state Snapshot class should be completely opaque to the caller--no visible methods or properties--but its properties have to be visible within the MachineModel class. I could obviously do this by downcasting, i.e. have CurrentSnapshot return an object, and have RestoreSnapshot accept an object argument which it casts back to a Snapshot.

But forced casting like that makes me feel dirty. What's the best alternate design that allows me to be both type-safe and opaque?

Update with solution:

I wound up doing a combination of the accepted answer and the suggestion about interfaces. The Snapshot class was made a public abstract class, with a private implementation inside MachineModel:

public class MachineModel
{
    public abstract class Snapshot
    {
        protected internal Snapshot() {}
        abstract internal void Restore(MachineModel model);
    }

    private class SnapshotImpl : Snapshot
    {
        /* etc */
    }

    public void Restore(Snapshot state)
    {
        state.Restore(this);
    }
}

Because the constructor and methods of Snapshot are internal, callers from outside the assembly see it as a completely opaque and cannot inherit from it. Callers within the assembly could call Snapshot.Restore rather than MachineModel.Restore, but that's not a big problem. Furthermore, in practice you could never implement Snapshot.Restore without access to MachineModel's private members, which should dissuade people from trying to do so.

You could reverse the dependency and make Snapshot a child (nested class) of MachineModel. Then Snapshot only has a public (or internal) Restore() method which takes as a parameter an instance of MachineModel. Because Snapshot is defined as a child of MachineModel, it can see MachineModel's private fields.

To restore the state, you have two options in the example below. You can call Snapshot.RestoreState(MachineModel) or MachineModel.Restore(Snapshot)*.

public class MachineModel
{
    public class Snapshot
    {
        int _mmPrivateField;

        public Snapshot(MachineModel mm) 
        { 
            // get mm's state
            _mmPrivateField = mm._privateField;
        }

        public void RestoreState(MachineModel mm) 
        { 
            // restore mm's state
            mm._privateField = _mmPrivateField;
        }
    }

    int _privateField;

    public Snapshot CurrentSnapshot
    {
        get { return new Snapshot(this); }
    }

    public void RestoreState(Snapshot ss)
    {
        ss.Restore(this);
    }
}

Example:

    MachineModel mm1 = new MachineModel();
    MachineModel.Snapshot ss = mm1.CurrentSnapshot;
    MachineModel mm2 = new MachineModel();
    mm2.RestoreState(ss);

* It would be neater to have Snapshot.RestoreState() as internal and put all callers outside the assembly, so the only way to do a restore is via MachineModel.RestoreState(). But you mentioned on Jon's answer that there will be callers inside the same assembly, so there isn't much point.

Return an opaque object to the caller without violating type-safety, Return an opaque object to the caller without violating type-safety. opaque return type swift function declares an opaque return type swiftui associated type swift An important proviso here is that functions with opaque return types must always return one specific type. If for example we tried to use Bool.random() to randomly launch an XWing or a YWing then Swift would refuse to build our code because the compiler can no longer tell what will be sent back.

Can MachineModel and Snapshot be in the same assembly, and callers in a different assembly? If so, Snapshot could be a public class but with entirely internal members.

‍🤝‍ 🧖 Return an opaque object to the caller without violating type , You can change the dependency and make Snapshot a child (nested class) of MachineModel. Then Snapshot has only the public (or internal) method Restore()​  0 Return an opaque object to the caller without violating type-safety Dec 3 '18 0 wpf textblock text is not clear when using layouttransform Apr 5 '18 0 Share the generic type with nested classes Apr 13 '18

I could obviously do this by downcasting, i.e. have CurrentSnapshot return an object, and have RestoreSnapshot accept an object argument which it casts back to a Snapshot.

The problem is that somebody could then pass an instance of an object which is not Snapshot.

If you introduce an interface ISnapshot which exposes no methods, and only one implementation exists, you can almost ensure type-safety at the price of a downcast.

I say almost, because you can not completely prevent somebody from creating another implementation of ISnapshot and pass it, which would break. But I feel like that should provide the desired level of information hiding.

Opaque Types, A function or method with an opaque return type hides its return value's type information. and code that calls into the module, because the underlying type of the return value can You could use generics to implement operations like flipping a shape to the Shape protocol, without specifying any particular concrete type. For custom Java classes with _SQL_TYPECODEof STRUCT, REF, or ARRAY(in other words, for custom Java classes that represent objects, object references, or collections), the class has a constant that indicates the relevant user-defined type name.

This is an old question, but i was looking for something very similar and I ended up here and between the information reported here and some other I came up with this solution, maybe is a little overkill, but this way the state object is fully opaque, even at the assembly level

class Program
{
    static void Main(string[] args)
    {
        DoSomething l_Class = new DoSomething();

        Console.WriteLine("Seed: {0}", l_Class.Seed);

        Console.WriteLine("Saving State");

        DoSomething.SomeState l_State = l_Class.Save_State();

        l_Class.Regen_Seed();

        Console.WriteLine("Regenerated Seed: {0}", l_Class.Seed);

        Console.WriteLine("Restoring State");

        l_Class.Restore_State(l_State);

        Console.WriteLine("Restored Seed: {0}", l_Class.Seed);

        Console.ReadKey();
    }
}

class DoSomething
{
    static Func<DoSomething, SomeState> g_SomeState_Ctor;

    static DoSomething()
    {
        Type type = typeof(SomeState);
        System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type.TypeHandle);
    }

    Random c_Rand = new Random();

    public DoSomething()
    {
        Seed = c_Rand.Next();
    }

    public SomeState Save_State()
    {
        return g_SomeState_Ctor(this);
    }

    public void Restore_State(SomeState f_State)
    {
        ((ISomeState)f_State).Restore_State(this);
    }

    public void Regen_Seed()
    {
        Seed = c_Rand.Next();
    }

    public int Seed { get; private set; }

    public class SomeState : ISomeState
    {
        static SomeState()
        {
            g_SomeState_Ctor = (DoSomething f_Source) => { return new SomeState(f_Source); };
        }

        private SomeState(DoSomething f_Source) { Seed = f_Source.Seed; }

        void ISomeState.Restore_State(DoSomething f_Source)
        {
            f_Source.Seed = Seed;
        }

        int Seed { get; set; }
    }

    private interface ISomeState
    {
        void Restore_State(DoSomething f_Source);
    }
}

Proceedings of the 1990 ACM Conference on LISP and Functional , An abstraction selected from a module is not convertible to its representation. the representation to violate invariants required by the module implementation. of a value remained the same, then the language would no longer be type safe. The type of the value returned from a with expression may contain references to  Those roles are reversed for a function with an opaque return type. An opaque type lets the function implementation pick the type for the value it returns in a way that’s abstracted away from the code that calls the function. For example, the function in the following example returns a trapezoid without exposing the underlying type of that shape.

Why use an opaque "handle" that requires casting in a public API , using a handle approach does not requiring using a single handle type for all the pointee (including calls to free ) whereas handing over a handle requires that of having a single type of handle, defined via a typedef is not type safe, and may case 1: // treat handle as SimpleEngine return start_simple_engine(​handle);  In any event the caller is free to modify the array without affecting the values held in the OPAQUE. Parameters: type - the SQLStructType used to convert the type to

The Literary Digest, of a clairvoyant's ability to read the contents of a letter without breaking the seal​, my return to Montpellier from Narbonne, without giving the least indication of I put through the flaps of the envelope a safety-pin, which served as a kind of be a case of reading through opaque bodies, giving the word opaque not only its​  Opaque objects are accessed via handles, which exist in user space. MPI procedures that operate on opaque objects are passed handle arguments to access these objects. In addition to their use by MPI calls for object access, handles can participate in assignment and comparisons. In Fortran, all handles have type INTEGER. In C, a different handle

The Continuing Arms Race: Code-Reuse Attacks and Defenses, For performance reasons, we handle return addresses stored on the stack separately from the rest of the code pointers using a safe stack mechanism (​Section 4.4.3). void*, char*, and opaque pointers to forward-declared structs or classes). to point to objects of any type, but such pointers are also used for C strings. IoCreateFile function. 02/13/2020; 11 minutes to read; In this article. The IoCreateFile routine either causes a new file or directory to be created, or it opens an existing file, device, directory, or volume, giving the caller a handle for the file object.

Comments
  • MachineModel will have callers both from within the same assembly and from outside assemblies.
  • Funny, I just wrote the code below and was curious how others would write it so I googled. I did exactly what you said..