I tried to run this code
var a = const [1, 2, 3];
a.add(10);
print(a);
but I am having an error. As I knew that assigning and const to a variable doesn't make it a const.
So why is this error occurring?
Unhandled exception: Unsupported operation: Cannot add to an
unmodifiable list
Variables in Dart is always references and the type of the variable does not change the state of the object it points to.
So in your case, you have declared a const list which means it is a compile-constant defined list and is therefore implicit unmodifiable.
You now point to this list by using a normal variable. But the type of the variable does not change the fact that your List is created as const from the beginning.
Related
I am trying out the example given in the Freezed page at https://pub.dev/packages/freezed.
This is the example I am testing replacing int with List<int> adding a [40] as default.
#Freezed(makeCollectionsUnmodifiable: false)
class Example with _$Example {
factory Example([#Default([40]) List<int> list]) = _Example;
factory Example.fromJson(Map<String, dynamic> json) => _$ExampleFromJson(json);
}
using the Example class:
final e = Example(); //default parameter [40]
//e.list.add(42); //error producing line
print('e $e');
final f = Example([40]); //parameter same as default value
f.list.add(42);
print('f $f');
final g = Example([41]); //another parameter
g.list.add(42);
print('g $g');
The output is:
e Example(list: [40])
f Example(list: [40, 42])
g Example(list: [41, 42])
If I remove the comment in the default parameter case, I get the following error:
Unhandled exception:
Unsupported operation: Cannot add to an unmodifiable list
#0 UnmodifiableListMixin.add (dart:_internal/list.dart:114:5)
#1 main (file:///D:/Dart/darttest/bin/darttest.dart:27:10)
#2 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:297:19)
#3 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:192:12)
Why the list becomes unmodifiable?
Dart annotations are used by code generators that parse the source code. As the expressions inside annotations do not get executed, they must be constant expressions that can be computed during compilation.
Objects constructed with the const keyword must be immutable. They cannot change, as is written in the Dart Language Tour:
You can’t change the value of a const variable:
baz = [42]; // Error: Constant variables can't be assigned a value.
Although a final object cannot be modified, its fields can be changed. In comparison, a const object and its fields cannot be changed: they’re immutable.
var constantList = const [1, 2, 3];
// constantList[1] = 1; // This line will cause an error.
To provide a default list value in the annotation, it must be const and therefore unmodifiable.
A note on Freezed
On another note, modifying lists goes against the entire Freezed concept. Freezed is used to generate immutable data structures, that can be updated with the copyWith method. It's a mistake to modify any collections in Freezed classes.
A recent update does add mutability through the unfreezed annotation, which should be used for mutable classes.
I am trying to assign a List as a value to a field in a Map.
However, if I vary the types in the Map, the methods seem to be no longer available on the List.
Example:
var list = [1,2];
var myMap = {
'list' : list,
//'commentOut' : 3,
};
print("${myMap['list'].runtimeType}");
print("${myMap['list']!.length}");
Output
JSArray<int>
2
However, if I uncomment the line 'commentOut' : '
I get the error
Error: The getter 'length' isn't defined for the class 'Object'.
Is there a way to get this to work, or am I misunderstanding something fundamental we can't do with Maps ?
When you do:
var list = [1,2];
var myMap = {
'list' : list,
};
then the inferred type of myMap is Map<String, List<int>>.
When you instead do:
var myMap = {
'list' : list,
'commentOut' : 3,
};
your Map's values are now heterogeneous, so Dart infers the value type as the nearest common base type. In this case, the common base type of List<int> and int is Object, so myMap's inferred type is Map<String, Object>.
Consequently, static analysis tools (the Dart analyzer and the Dart compiler) know only that the static (known at compile-time) type of myMap['list'] is Object? even though the actual runtime type is List<int>. It therefore will be a compile-time error to attempt to directly access List methods on it, because calling List methods on an arbitrary Object is potentially unsafe.
You either must explicitly cast the value (myMap['list'] as List<int>) or disable static type checking by explicitly declaring myMap to be of type Map<String, dynamic>.
3 is a value type, not an object type. So the property "length" is not defined on a value type. Even though you defined the element 'list' to get the length, it doesn't know how to handle it.
I was going through the Dart language tour and noticed that they have the statement
final constantSet = const {
'fluorine'
};
and I was just wondering whether there is a programmatic difference in declaring the constant as final or whether that has a specific purpose. I can see that we can place const either before or after the variable declaration, is this the same thing, considering that when you declare a variable as final it is only able to be initialized once anyway?
There is a difference.
A final constantSet = const {'fluorine'}; declares a non-constant final variable bound to a value which happens to be a compile-time constant.
A const constantSet = const {'fluorine'}; declares a constant variable bound to a compile-time constant value (and the second const can be omitted).
The latter allows you to use constantSet in constant expressions, so const [costantSet] is only valid with the latter declaration.
That also means that declaring a variable as const is something you cannot take back. Changing a const declaration to final may break code using the const variable in constant expression. That's a reason for not making every variable const, just because it can be. You might not want to promise that it stays constant forever.
Take this example:
void modl(List<int> l) {
l.add(90);
print(l);
}
class Foo {
final List<int> bar;
const Foo(this.bar);
#override
String toString() => 'Foo{bar: $bar}';
}
void main() {
var foo = const Foo([1,2,3,4]);
modl(foo.bar);
print (foo);
}
Running the above code results in a runtime Uncaught Error, but removing the const from
var foo = const Foo([1,2,3,4]);
allows it to work.
This seems like a bug to me because the const variable can be mutated and dart detects this at runtime, which means it has the means to detect when a const object is modified, but shouldn't this have been detected at compile time, seeing as const variables are called "compile-time constants".
If this is not a bug, is there anything in dart that allows us to detect at compile time when a const variable will possibly be mutated by an operation?
In C++, the compiler errors out when we try to do something like this. Is there anything we can do in Dart to avoid encountering this error at runtime?
No. Dart const is a compile-time feature around object creation, but it's not reflected in the type system.
You can't tell from the type of any object whether it's a constant or not.
Usually that's not a problem because an instance of a class which can be const is unmodifiable. It's not guaranteed to be deeply immutable, but the instance itself cannot have its fields changed.
Lists, sets and maps can both be either constant and mutable. That's what you are seeing here.
The list argument to const Foo(const [1, 2, 3, 4]) is constant, even if you remove the redundant const on the list literal. You would have the same issue with new Foo(const [1, 2, 3, 4]), which would also provide an immutable foo.bar, but which would otherwise be indistinguishable from new Foo([1, 2, 3, 4]). The only real difference is whether the list is modifiable or not, and the only way to detect that is to try to modify it.
Lists, sets and maps do not provide any way to detect whether they are mutable or not except trying, and catching the error.
When comparing to C++, Dart's notion of being const is a property of the object or, really, the way the object is created. That may have some consequences for the object. A const Foo(..) just creates a normal Foo object, but it can only create deeply immutable objects, and they are canonicalized. A const [...] or const {...} creates a different kind of list/map/set than the non-const literal, but that's not visible in the type.
In C++, being const is a property of an object reference, and it restricts how that reference can be used, but there are no constant objects as such. Any object can be passed as a const reference.
The two concepts are completely different in nature, and just happen to use the same name (and are also both different from JavaScript const).
The analyzer doesn't say final var is illegal.
but dart2js says final var is illegal
What is correct? Why?
The keyword var means mutable variable with explicit dynamic type specifier.
The explicit type specifier means that this is not possible specify another type in declaration.
The keyword final means val or immutable variable with unspecified type, with implicit dynamic type.
The implicit type specifier means that this is possible specify other type in declaration.
More precisely variable that declared as val are the value and variable at once.
It are variable because has the runtime storage.
But it are also immutable value that can be retrieved from associated storage just once and can be used anywhere.
Now consider the following code:
final var foo;
This is the same as the following pseudo code:
immutable mutable dynamic foo;
Of course, this will not work.
That is probably a bug in the analyzer. final and var are mutual exclusive.
One of the following is allowed
final identifier
final type identifier
const identifier
const type identifier
var identifier
type identifier
Dart Programming Language Specification (1.2) - Variables
finalConstVarOrType:
final type?
| const type?
| varOrType
;
varOrType:
var
| type
;
EDIT
My DartEditor (Dart VM version: 1.3.0-dev.3.2 (Mon Mar 10 10:15:05 2014) on "linux_x64") shows an error for final var xxx (Members can not be declared to be both 'final' and 'var'.)