This is invalid code because Dart requires MyInterface.foo to be initialized even though its abstract.
abstract class MyInterface {
final String foo;
}
class MyClass implements MyInterface {
final String foo;
MyClass(this.foo);
}
This is invalid code because MyClass doesn't have a setter for MyInterface.foo
abstract class MyInterface {
String foo; // remove final
}
class MyClass implements MyInterface {
final String foo;
MyClass(this.foo);
}
This is valid code but it is confusing and ugly.
abstract class MyInterface {
final String foo = null; // add back final, add set to null
}
class MyClass implements MyInterface {
final String foo;
MyClass(this.foo);
}
What about
abstract class MyInterface {
String get foo;
}
That does work but it's not obvious to me why a getter can be done with a final but a final cannot be done with a getter or a final. I'll defer to others to decide if this warrants a change.
My personal opinion is that the implementation should be able to match the abstract interface exactly to improve clarity.
Glad to know that the getter works, though! :)
Sounds like https://github.com/dart-lang/language/issues/45
It is indeed dart-lang/language#45.
The problem you run into here is that a final field must be initialized, either with an initializer expression or by all generative constructors.
The final field foo in your abstract MyInterface class is not initialized, so your program has an error.
It doesn't matter that you won't ever use the class for anything other than its interface, the rule is strict.
If someone somewhere decided to extend your abstract class, they would not be able to get the field initialized.
That's why changing the field to be non-final, initializing it or changing it to a getter signature all work, the class no longer contains an uninitialized final field. Using a getter signature is the recommended solution.
We could change rules to make this easier. The largest impact change would be to change grammar for property getters/setters and allow you to write something like:
abstract class MyInterface {
String foo { get; }
}
It's still further from an abstract field.
Another option is to reintroduce the interface keyword, which would make it clear that it is not a class that can be extended, so the field must be abstract:
interface MyInterface {
final String foo;
}
Another option is to allow abstract on a field declaration to mark it abstract (for all other members you can omit the body, but there is no body for a field to omit).
abstract class MyInterface {
abstract final String foo;
}
Or, maybe we can say that having a non-initialized field is only an error for concrete (non-abstract) classes. Since MyInterface is abstract, it's allowed to have a non-initialized final field, but any concrete class extending it will be an error. It makes it easier to forget initializing a field in a class intended as an abstract superclass, but I guess proper testing would catch that.
That rule would have no other reason to exist than to allow abstract classes to be used only for their interfaces, but it can then be used to prevent extending the class (which isn't new, you could already just omit having a public generative constructor).
Currently, all you can do is use the getter.
I personally like how dart has implicit interfaces. I cannot provide a good reason why, it just seems cleaner. I like that abstract classes can be used for everything.
That said, my understanding is that abstract classes cannot use a constructor and thus there would be no scenario where you'd initialize a final property. For that reason I just assumed that I could write my abstract classes like a blueprint, like a pre-paste copy-paste, with finals etc and consume it as an interface without issues.
Is there a scenario I am missing where an abstract class will initialize a final property?
Cheers
Besides a personal obsession with the idea of an abstract class as an interface working like a copy-paste, using the getter does make sense and does seem reasonable. It feels quite Darty.
Abstract classes can have a constructor. You can't call it if you use it as interface though.
Otherwise you invoke it with super() in the sub class.
Whoops. Yeah, I am a little embarrassed that I missed that.
One advantage of implicit interfaces is that it has low "migration cost". If you start something out as an "interface" (a fully abstract class), and you then decide to provide a default implementation for some functions, you can just do that in the same class. You don't have to invent a new class.
Having two ways to do the same thing, one simple and limited, and one complex and powerful, can indirectly drive API designs to stay within the limited capabilities of the simple approach rather than risk the cost of having to convert everything to the more complex design.
This is one place where our enum classes fail. Dart enums are very simplistic. If you want more than that, then you have to rewrite everything to a normal class where you declare each constant manually, even if it's only to add one method to every value. The Java enum feature allows organically adding more features to an initially simple syntax without forcing a sudden and expensive migration. That is why it is better, not just because it's more powerful (it's no more powerful than full classes anyway), but because it can evolve better.
then you have to rewrite everything to a normal class
I don't see this as a big issue except that it's not treated like an enum (switch/case, autocompletion)
@zoechi the value of those dev tools cannot be understated. That's basically all TypeScript is, one massive autocomplete engine, and it's incredibly successful. Improving dx is important to me because it improves productivity and thus the value of the language.