Dart from the ground up

Functions and their parameters

Naming a piece of work, giving it inputs and an answer, letting a caller leave an argument out, and passing arguments by name so the call reads like a sentence.

Part 6 of 12

Intermediate18 min readBack to Courses

A name for a piece of work

A function takes inputs, does one thing, and hands back an answer. The line String greet(String name) { ... } reads as: the type of the answer, the name of the function, then the parentheses holding each input with its type. Everything outside main in the file is a top-level function, and any function can call any other.

The word return hands the answer back and ends the function on that line. When the body is a single expression, an arrow replaces the braces and the word return: the expression itself is the answer, which is why a one-line rule reads so well in that shape.

Leaving an argument out

A parameter may carry a default value, and then it is optional. Square brackets hold optional positional parameters: String greet(String name, [String role = "reader"]). A caller who agrees with the default writes greet("Ada") and one who wants something else writes greet("Ada", "pilot").

Curly braces hold named parameters, which the caller spells out: int total(int cents, {int qty = 1}) is called as total(450, qty: 3). The order of named arguments does not matter, and the call says what each value means. A named parameter with no default must be marked required so the compiler can insist on it.

Why write one at all

Write a function when you can say what it does in a few words. A name like total or greet lets a reader skip the body and trust the call, and that trust is the only reason to move code out of main.

It also gives the rule one home. When the tax rate changes, a change inside one function reaches every call, while a rule copied into three places has to be found and changed three times — and the copy that is missed is the one that ships.

Values go in, one value comes out

When you call a function, each argument is evaluated and handed to the matching parameter. For an int, a double or a String, the function receives a copy: assigning a new value to the parameter inside the body does not touch the caller. That is why a function can use a short name like n without worrying about what the caller called it.

A list or a map is different. The value handed over is a reference to the same collection, so adding to a list inside a function is visible to the caller afterwards. That can be useful, and it is also how a function quietly changes something it was only asked to read. Return a new list when the caller should keep the original.

return ends the function and hands the value back. A function declared void hands nothing back and is used for its effect, usually printing. A non-void function must return on every path, which the compiler checks for you.

Small functions, called in a chain

A function with one job is easy to name, and a name is what lets a reader skip its body. money turns cents into text, lineTotal works out one line, and line puts the two together. None of them knows the internals of the others, so each can be read and tested alone.

Composition is calling one from another. line calls lineTotal, and lineTotal uses nothing but its arguments. The chain reads from the outside in: the report asks for a line, the line asks for a total, the total does the arithmetic.

Required named parameters are the ones with no default. Mark them required and the compiler insists that the caller supplies them, which is better than a default that silently means "missing". Optional positional parameters and named parameters are different tools; do not mix them in one signature unless the call still reads clearly.

A default, a named argument and an arrow

String greet(String name, [String role = "reader"]) {
  return name + " is a " + role;
}

int total(int cents, {int qty = 1}) => cents * qty;

void main() {
  print(greet("Ada"));
  print(greet("Ada", "teacher"));
  print("qty 4: " + total(450, qty: 4).toString());
}

One function, three kinds of call

Run it and match each printed line to its call. Then add a function of your own with two parameters and one default, and call it once with everything given and once with only the first argument.

Output
Click "Run" to execute your code

Three function mistakes. Forgetting return on one path of a non-void function, which the compiler reports as a body that may complete without a value. Putting a positional argument after a named one in a call, which is not allowed. And expecting a function that changes an int parameter to change the caller variable — primitives are copied, collections are shared.

Try these next

  • Write cube(int n) as an arrow function and print cube(4).
  • Add a required named parameter to line and let the compiler show you the call that now fails.
  • Write a function that takes a list of cents and returns the largest, then call it twice with different lists.

A receipt built from small functions

Run it and follow one line back through the calls that produced it. Then add a fourth item and a function of your own that prints the item count, and call it from main.

Output
Click "Run" to execute your code

Most of the file is the function signature rather than the body. In Dart the names and the types are the documentation a reader trusts, so a function whose signature is clear rarely needs a comment above it.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What does a default value let a caller do?