Nic.Star
Nic.Star

Reputation: 160

Is there any programming language where you can forbid class casting of return types?

Background

Take this simple java class as an example:

class SomeWrapper {

    List<SomeType> internalDataStructure = new ArrayList<>();
    boolean hasSomeSpecificSuperImportantElement = false;
    // additional fields that do useful things

    public void add(SomeType element) {
        internalDataStructure.add(element);
        if (SUPER_IMPORTANT.equals(element)) {
            hasSomeSpecificSuperImportantElement = true;
        }
    }

    public Iterable<SomeType> getContents() {
        return internalDataStructure;
    }

    // additional methods that are definitely useful

}

Although it looks somewhat okay it has a big flaw. Because internalDataStructure is returned as is, callers of getContents() could class cast it to List and modify the internal data structure in ways they are not supposed to.

class StupidUserCode {

    void doSomethingStupid(SomeWrapper arg) {
        Iterable<SomeType> contents = arg.getContents();

        // this is perfectly fine
        List<SomeType> asList = (List<SomeType>) contents;

        asList.add(SUPER_IMPORTANT);
    }

}

In java and in other, similar programming languages, this problem can be solved by wrapping the internal data structure in some immutable wrapper. For example:

    public Iterable<SomeType> getContents() {
        return Collections.unmodifiableList(internalDataStructure);
    }

This works now, but has, in my humble opinion, a number of drawbacks:

Question

Are there any programming languages where you can specify a return type of a method to be impossible to be class cast?

I was thinking of something like synthetic types where appending an exclamation mark ! to the end of a typename makes it un-class-cast-able. Like this:

    public Iterable!<SomeType> getContents() {
        return internalDataStructure;
    }

    void doSomethingStupid(SomeWrapper arg) {
        // The ! here is necessary because Iterable is a different type than Iterable!
        Iterable!<SomeType> contents = arg.getContents();

        // this now becomes a compile-time error because Iterable! can not be cast to anything
        List<SomeType> asList = (List<SomeType>) contents;

        asList.add(SUPER_IMPORTANT);
    }

Upvotes: 2

Views: 54

Answers (1)

kaya3
kaya3

Reputation: 51034

Since the point here is about mutability, the Java example of List vs. Iterable isn't a great example of mutable vs. immutable data types (the Iterator.remove method mutates the underlying collection, so the List could be corrupted by the external caller even without casting).

Let's instead imagine two types, MutableList and ReadonlyList, where MutableList is the subtype, and a ReadonlyList only prevents the user from mutating it; the list itself is not guaranteed to avoid mutation. (We cannot sensibly name the supertype ImmutableList because no value is both a mutable and an immutable list.)


Casting from the supertype to the subtype, e.g. from ReadonlyList to MutableList, is called downcasting. Downcasting is unsafe, because not every value of the supertype is a value of the subtype; so either a check needs to be performed at runtime (as Java does, throwing ClassCastException if the instance being casted does not have the right type), or the program will do something memory-unsafe (as C might do).

In theory, a language might forbid downcasting on the grounds that it is unsafe; popular programming languages don't, because it's convenient to be able to write code which you know is type-safe, but the language's type system is not powerful enough for you to write suitable type annotations which allow the type-checker to prove that the code is type-safe. And no type-checker can reasonably be expected to prove every provable property of your code. Still, in theory there is nothing stopping a language from forbidding downcasts; I just don't think many people will choose to use such a language for developing large software.

That said, I think the solution to the problem you describe would be simply not to make MutableList a subtype of ReadonlyList. The MutableList class can still have a method to get a read-only view, but since there would be no subtype/supertype relation, that view would not be a value of type MutableList so it could not be cast to the mutable type, even if you upcast to a common supertype first.

To avoid the performance cost at runtime, it could be possible for a language to have specific support for such wrappers to allow the wrapper to delegate its methods to the original list at compile-time instead of at runtime.

Upvotes: 2

Related Questions