2

Let's take a look at this code

interface ImmutableValue<T> {
    readonly value: T;
}

let i: ImmutableValue<string> = { value: "hi" };
i.value = "Excellent, I can't change it"; // compile-time error

The above is pretty straight forward, we have an interface ImmutableValue and since i is implementing that interface, i.value will be getting compilation error

Now consider the below code

interface Greetable {
  readonly name: string;
}

class Person implements Greetable {
  name: string;

  constructor(n: string) {
    this.name = n;
  }
}

let user1 = new Person('isaac');

user1.name = 'jon'
console.log(user1)

For the code above, clearly Person is implementing Greetable which has a name of readonly and user1.name = 'jon' is a value reassignment and we should get an error. But TS is compiling perfectly fine, any idea why is that?

3
  • 1
    Person extends Greetable, including allowing writing to name. Commented Aug 7, 2021 at 8:37
  • @jonrsharpe: I don't think we can extends an interface.. Commented Aug 7, 2021 at 8:52
  • 2
    I don't mean extends, I mean in the sense of adding behaviour. Anything that expects a Greetable, something that has a readable name property, can still use Person. They don't be be able to write through that interface, the fact that another consumer accessing the same value through Person could is irrelevant. Commented Aug 7, 2021 at 8:53

1 Answer 1

1

TypeScript ignores readonly when determining whether two types are compatible, so Person is compatible with Greetable despite Greetable making name readonly while Person doesn't. From this page on readonly:

It’s important to manage expectations of what readonly implies. It’s useful to signal intent during development time for TypeScript on how an object should be used. TypeScript doesn’t factor in whether properties on two types are readonly when checking whether those types are compatible, so readonly properties can also change via aliasing.

(my emphasis)

More generally, though, a subtype can add features on top of its supertype. As jonrsharpe points out in a comment, any code that uses a Greetable can still use a Person. Code with a Greetable reference to Person won't be able to change name through that reference:

function example(g: Greetable) {
    g.name = "example"; // Error
}
example(new Person("Joe"));

but being publicly readonly doesn't guarantee that something won't change, just that code that has readonly access to it can't change it, which is subtly different.

Sign up to request clarification or add additional context in comments.

2 Comments

hmm interesting...my understanding of an interface was that, it is to be used to enforce Person to have similar signature as Greetable. If it's inheritance by extends, the part about definition overriding I can understand, but here is nothing to do with inheritance isnt it
@Isaac - Overriding is related to inheritance, yes, although I think overriding is a bit tangential to this particular aspect and I should probably just reword it out of the answer. :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.