Alexey Subbotin
Alexey Subbotin

Reputation: 857

Should I mark all method parameters as final and specify type

Effective dart specifies that top-level variables should be final when applicable: https://dart-lang.github.io/linter/lints/prefer_final_fields.html

However, I could not find any information about method parameters.

Flutter repo's code functions are mostly ones with parameters not marked final, that includes all the overridden build methods I've seen.

Which of the following is better in terms of performance and app weight:

@override build(context)

@override build(BuildContext context)

@override build(final BuildContext context)

Perhaps overridden functions should be defined the same way as the super function? Is there any difference between overridden functions like build above which can infer the type, and other named/unnamed functions (other than not setting the type makes the variable dynamic), which Flutter repo also writes this way:

static double _flingDistancePenetration(double t) { // t is not final, although treated as immutable
 return (1.2 * t * t * t) - (3.27 * t * t) + (_initialVelocityPenetration * t);
}

I've seen this question about Java: Why would one mark local variables and method parameters as "final" in Java?, and, while I agree with the top answer, I'm completely in the dark about why does Flutter repo not do that.

Upvotes: 5

Views: 1655

Answers (2)

jamesdlin
jamesdlin

Reputation: 90005

Regarding parameter types: yes, always include them. Otherwise your parameter is likely to be dynamic1, which incurs runtime overhead and no type safety. You also want your API to clearly specify what argument types it expects.

Regarding final: That is an opinion-based question, and my opinion is that while I agree with the prefer_final_fields lint, I disagree with the prefer_final_locals lint. I think that adding final for local variables (including function/method parameters) is pointless.

  • Contrary to what the top-voted answer to the related Java question says, any half-decent compiler should be able to easily determine whether a local variable is reassigned. If there's any optimization opportunity there, the compiler should be able to do it for you.

  • In languages such as Dart where implementation usually is inline with the interface (as opposed to languages such as C or C++, which have separate header files to declare interfaces), adding final to parameters is visual noise. It provides no useful information to callers.

  • You shouldn't unilaterally mark all function/method parameters as final anyway. Sometimes reassigning parameters is appropriate. For example, if your function needs to perform some sort of normalization on its arguments:

    void foo(File file) {
      file = file.absolute;
      ...
    }
    

    in such a case, using a final File file would mean you'd need a separate local variable for the normalized version, and now the code would be error-prone since it could accidentally use the original variable.

  • final for a field is an important part of an API. It tells callers that the field will not be reassigned; the object referred to by the field will always be the same object. final for local variables and for function/method parameters affects only the person implementing the function. If the implementor doesn't want to reassign a variable, they can choose to simply not reassign it.

  • Some people will claim that having final local variables helps code readability since they know that the variable will not be reassigned. However, I instead think that final can be misleading since it says only that a variable cannot be reassigned, not that it won't be mutated, and knowing the latter is much more important.


1 For overridden methods, parameter types are constrained by the corresponding parameter types declared by the base class. Therefore omitting a parameter type in an overridden method will use the parameter type from the base class.

Upvotes: 7

mezoni
mezoni

Reputation: 11210

Answer to the author of the question.

  1. You should not mark all method parameters as final
  2. You should specify the type

Examples:

foo(Baz baz)

void main(List<String> args)

An exception to these rules.

  1. You can omit the type indication if the type is dynamic
  2. You can don't specify type if the type can be inferred automatically by the compiler, in the case of using an function-expression.

foo(Baz baz, bar)

list.sort((x, y) => x.compareTo(y))

Some useful information:

From Dart specification:
Dart Programming Language Specification 5th edition draft Version 2.2

A final variable is a variable whose binding is fixed upon initialization; a final variable v will always refer to the same object after v has been initialized. A variable is final iff its declaration includes the modifier final or the modifier const. A mutable variable is a variable which is not final.

Variable immutability does not make an object immutable in Dart.

This is a very common misconception when variable immutability is confused with object immutability.

P.S.

A common misconception is that Dart users get the impression that function parameters in Dart are passed by reference.
This is not true.
Parameters are passed by value (not by reference).
But due to the fact that (as indicated in the specification) any variable already contains a reference (refers) to the object (but not the object itself), in fact, this reference is passed, by value (as normal value).
That is, a value is passed, but the value itself is of the Referernce type, which is indicated in the language specification.

From Dart specification:

a final variable v will always refer to the same object

That is, the variable stores the reference, but not the value itself. It is this reference that is passed by value, because there is no reason to pass this reference by reference.

Upvotes: 2

Related Questions