Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Using functions and variables

Let’s expand our simple program a bit by using a function to determine who is greeted.

// File: examples/chapter_02_functions/hello_world.ob

fn greet(name: String) -> String {
    "Hello, #{name}!"
}

fn main() {
    let greeting = greet("Oxiby")

    print_line(greeting)
}

Again, use the Oxiby compiler to run the program and see its ouput:

$ obc run hello_world.ob
Hello, Oxiby!

This version of the program teaches us a few more things about Oxiby.

Function parameters

fn greet(name: String)

Function parameters are declared with an identifier beginning with a lowercase letter followed by a colon and then a type identifier beginning with an uppercase letter. In this case, our function greet has a single parameter called name which must be a String.

If a function has multiple parameters, they are separated by commas:

fn example(a: String, b: String)

Parameters like this are called positional parameters, because they must appear in the same order in the function definition and any time the function is called:

example("a", "b")

Functions can also have keyword parameters. These are distinguished from positional parameters by an extra colon before their identifer:

fn greet(:name: String)

The difference between these two styles is in how they are written when a function is called. While positional parameters must appear in the order they are defined, keyword parameters can be given in any order, because they include their identifiers at the call site:

fn example(x: String, y: String, :a: String, :b: String) {}

fn main() {
    example("positional x", "positional y", a: "keyword a", b: "keyword b")
}

The programmer can decide whether to use positional or keyword parameters as they choose. A good rule of thumb is to use positional parameters when a function has only one parameter, or when the order of parameters is obvious and easy to remember. When a function has multiple parameters without an obvious ordering, keyword parameters are a better choice.

When a function has parameters of both kinds, the keyword parameters must appear after the positional parameters in both the function definition and any time the function is called.

Function return values

fn greet(name: String) -> String

The type of value a function returns is declared by an arrow followed by a type identifier, written between the parameter list and the function body’s curly braces. In this case, our function returns a String.

The value returned by a function is its final expression. In our case, that’s the string "Hello, #{name}!". If we want, we can also use the return keyword to specify the returned value explicitly. This form can be used to “return early” from a function, even if it’s not the last line of the function. We’ll learn in a future chapter why we might want to do so.

return "Hello, #{name}!"

The combination of a function’s name, parameters, and return type is called its signature.

Variables

let name = "Oxiby"

A variable is created by binding a value to an identifier using let. The syntax is the keyword let followed by an identifier, an equals sign, and an expression. A variable name must start with a lowercase letter and should be written in snake case, using all lowercase letters and separating “words” with an underscore, e.g. example_variable_name.

let is actually much more powerful than this simple form, but we’ll explore that further in a few chapters.

String interpolation

"Hello, #{name}!"

Strings can contain interpolations, expressions embedded inside literal text, by delimiting the expression with #{ and }. These expressions must evaluate to a String.