Functions
As mentioned in the syntax overview, Mojo supports two
keywords to declare functions: def
and fn
. You can use either declaration
with any function, including the main()
function, but they have different
default behaviors, as described on this page.
We believe both def
and fn
have good use cases and don't consider either to
be better than the other. Deciding which to use is a matter of personal taste as
to which style best fits a given task.
Anatomy of a function
Both def
and fn
function declarations have the same basic components (here
demonstrated with a def
function):
def function_name[ parameters ... ]( arguments ... ) -> return_value_type: function_body
Functions can have:
- Parameters: A function can optionally take one or more compile-time parameter values used for metaprogramming.
- Arguments: A function can also optionally take one or more run-time arguments.
- Return value: A function can optionally return a value.
- Function body: Statements that are executed when you call the function. Function definitions must include a body.
All of the optional parts of the function can be omitted, so the minimal function is something like this:
def do_nothing():
pass
def do_nothing():
pass
If a function takes no parameters, you can omit the square brackets, but the parentheses are always required.
Although you can't leave out the function body, you can use the pass
statement
to define a function that does nothing.
Arguments and parameters
Functions take two kinds of inputs: arguments and parameters. Arguments are familiar from many other languages: they are run-time values passed into the function.
def add(a: Int, b: Int) -> Int:
return a+b
def add(a: Int, b: Int) -> Int:
return a+b
On the other hand, you can think of a parameter as a compile-time variable that becomes a run-time constant. For example, consider the following function with a parameter:
def add_tensors[rank: Int](a: MyTensor[rank], b: MyTensor[rank]) -> MyTensor[rank]:
# ...
def add_tensors[rank: Int](a: MyTensor[rank], b: MyTensor[rank]) -> MyTensor[rank]:
# ...
In this case, the rank
value needs to be specified in a way that can be
determined at compilation time, such as a literal or expression.
When you compile a program that uses this code, the compiler produces a unique
version of the function for each unique rank
value used in the program, with
rank
treated as a constant within each specialized version.
This usage of "parameter" is probably different from what you're used to from other languages, where "parameter" and "argument" are often used interchangeably. In Mojo, "parameter" and "parameter expression" refer to compile-time values, and "argument" and "expression" refer to run-time values.
By default, both arguments and parameters can be specified either by position or by keyword. These forms can also be mixed in the same function call.
# positional
x = add(5, 7) # Positionally, a=5 and b=7
# keyword
y = add(b=3, a=9)
# mixed
z = add(5, b=7) # Positionally, a=5
# positional
x = add(5, 7) # Positionally, a=5 and b=7
# keyword
y = add(b=3, a=9)
# mixed
z = add(5, b=7) # Positionally, a=5
For more information on arguments, see Function arguments on this page. For more information on parameters, see Parameterization: compile-time metaprogramming.
def
and fn
comparison
Defining a function using def
and fn
have much in common. They both have the
following requirements:
-
You must declare the type of each function parameter and argument.
-
If a function doesn't return a value, you can either omit the return type or declare
None
as the return type.# The following function definitions are equivalent
def greet(name: String):
print("Hello," name)
def greet(name: String) -> None:
print("Hello," name)# The following function definitions are equivalent
def greet(name: String):
print("Hello," name)
def greet(name: String) -> None:
print("Hello," name) -
If the function returns a value, you must either declare the return type using the
-> type
syntax or provide a named result in the argument list.# The following function definitions are equivalent
def incr(a: Int) -> Int:
return a + 1
def incr(a: Int, out b: Int):
b = a + 1# The following function definitions are equivalent
def incr(a: Int) -> Int:
return a + 1
def incr(a: Int, out b: Int):
b = a + 1For more information, see the Return values section of this page.
Where def
and fn
differ is error handling and argument mutability defaults.
-
The compiler doesn't allow a function declared with
fn
to raise an error condition unless it explicitly includes araises
declaration. In contrast, the compiler assumes that all functions declared withdef
might raise an error. See the Raising and non-raising functions section of this page for more information. -
All arguments to a function declared with
fn
are immutable references by default (that is, values are read-only, using theread
argument convention). This prevents accidental mutations, and permits the use of non-copyable types as arguments.All arguments to a function declared with
def
are mutable. Arguments default to using theread
argument convention like anfn
function, with a special addition: if the function mutates the argument, it makes a mutable copy.You can override the default behavior for both
def
andfn
functions by providing an explicit argument convention when declaring the argument.
As far as a function caller is concerned, there is no difference between
invoking a function declared with def
vs a function declared with fn
. You
could reimplement a def
function as an fn
function without making any
changes to code that calls the function.
Function arguments
As noted in the previous section, there is a difference between how def
and fn
functions handle default argument conventions.
Argument conventions are discussed in much more detail in the page on
Ownership.
The remaining rules for arguments described in this section apply to both def
and fn
functions.
Optional arguments
An optional argument is one that includes a default value, such as the exp
argument here:
fn my_pow(base: Int, exp: Int = 2) -> Int:
return base ** exp
fn use_defaults():
# Uses the default value for `exp`
var z = my_pow(3)
print(z)
fn my_pow(base: Int, exp: Int = 2) -> Int:
return base ** exp
fn use_defaults():
# Uses the default value for `exp`
var z = my_pow(3)
print(z)
However, you can't define a default value for an argument that's declared with
the mut
argument
convention.
Any optional arguments must appear after any required arguments. Keyword-only arguments, discussed later, can also be either required or optional.
Keyword arguments
You can also use keyword arguments when calling a function. Keyword arguments
are specified using the format
argument_name = argument_value
.
You can pass keyword arguments in any order:
fn my_pow(base: Int, exp: Int = 2) -> Int:
return base ** exp
fn use_keywords():
# Uses keyword argument names (with order reversed)
var z = my_pow(exp=3, base=2)
print(z)
fn my_pow(base: Int, exp: Int = 2) -> Int:
return base ** exp
fn use_keywords():
# Uses keyword argument names (with order reversed)
var z = my_pow(exp=3, base=2)
print(z)
Variadic arguments
Variadic arguments let a function accept a variable number of arguments. To
define a function that takes a variadic argument, use the variadic argument
syntax *argument_name
:
fn sum(*values: Int) -> Int:
var sum: Int = 0
for value in values:
sum = sum + value
return sum
fn sum(*values: Int) -> Int:
var sum: Int = 0
for value in values:
sum = sum + value
return sum
The variadic argument values
here is a placeholder that accepts any number of
passed positional arguments.
You can define zero or more arguments before the variadic argument. When calling the function, any remaining positional arguments are assigned to the variadic argument, so any arguments declared after the variadic argument can only be specified by keyword (see Positional-only and keyword-only arguments).
Variadic arguments can be divided into two categories:
- Homogeneous variadic arguments, where all of the passed arguments are the same
type—all
Int
, or allString
, for example. - Heterogeneous variadic arguments, which can accept a set of different argument types.
The following sections describe how to work with homogeneous and heterogeneous variadic arguments.
Homogeneous variadic arguments
When defining a homogeneous variadic argument, use *argument_name: argument_type
:
def greet(*names: String):
...
def greet(*names: String):
...
Inside the function body, the variadic argument is available as an iterable list
for ease of use. Currently there are some differences in handling the list
depending on whether the arguments are register-passable types (such as Int
)
or memory-only types (such as String
).
Register-passable types, such as Int
, are available as a
VariadicList
type. As
shown in the previous example, you can iterate over the values using a for..in
loop.
fn sum(*values: Int) -> Int:
var sum: Int = 0
for value in values:
sum = sum+value
return sum
fn sum(*values: Int) -> Int:
var sum: Int = 0
for value in values:
sum = sum+value
return sum
Memory-only types, such as String
, are available as a
VariadicListMem
.
Iterating over this list directly with a for..in
loop currently produces a
Pointer
to each value instead
of the value itself. You must add an empty subscript operator []
to
dereference the pointer and retrieve the value:
def make_worldly(mut *strs: String):
# Requires extra [] to dereference the pointer for now.
for i in strs:
i[] += " world"
def make_worldly(mut *strs: String):
# Requires extra [] to dereference the pointer for now.
for i in strs:
i[] += " world"
Alternately, subscripting into a VariadicListMem
returns the argument value,
and doesn't require any dereferencing:
fn make_worldly(mut *strs: String):
# This "just works" as you'd expect!
for i in range(len(strs)):
strs[i] += " world"
fn make_worldly(mut *strs: String):
# This "just works" as you'd expect!
for i in range(len(strs)):
strs[i] += " world"
Heterogeneous variadic arguments
Implementing heterogeneous variadic arguments is somewhat more complicated than homogeneous variadic arguments. Writing generic code to handle multiple argument types requires traits and parameters. So the syntax may look a little unfamiliar if you haven't worked with those features. The signature for a function with a heterogeneous variadic argument looks like this:
def count_many_things[*ArgTypes: Intable](*args: *ArgTypes):
...
def count_many_things[*ArgTypes: Intable](*args: *ArgTypes):
...
The parameter list, [*ArgTypes: Intable]
specifies that the function takes an
ArgTypes
parameter, which is a list of types, all of which conform to the
Intable
trait. The argument list,
(*args: *ArgTypes)
has the familiar *args
for the variadic argument, but
instead of a single type, its type is defined as list of types, *ArgTypes
.
This means that each argument in args
has a corresponding type in ArgTypes
,
so args[n]
is of type ArgTypes[n]
.
Inside the function, args
is available as a
VariadicPack
. The easiest
way to work with the arguments is to use the each()
method to iterate through
the VariadicPack
:
fn count_many_things[*ArgTypes: Intable](*args: *ArgTypes) -> Int:
var total = 0
@parameter
fn add[Type: Intable](value: Type):
total += Int(value)
args.each[add]()
return total
print(count_many_things(5, 11.7, 12))
fn count_many_things[*ArgTypes: Intable](*args: *ArgTypes) -> Int:
var total = 0
@parameter
fn add[Type: Intable](value: Type):
total += Int(value)
args.each[add]()
return total
print(count_many_things(5, 11.7, 12))
28
28
In the example above, the add()
function is called for each argument in turn,
with the appropriate value
and Type
values. For instance, add()
is first
called with value=5
and Type=Int
, then with value=11.7
and Type=Float64
.
Also, note that when calling count_many_things()
, you don't actually pass in
a list of argument types. You only need to pass in the arguments, and Mojo
generates the ArgTypes
list itself.
As a small optimization, if your function is likely to be called with a single
argument frequently, you can define your function with a single argument
followed by a variadic argument. This lets the simple case bypass populating and
iterating through the VariadicPack
.
For example, given a print_string()
function that prints a single string, you
could re-implement the variadic print()
function with code like this:
fn print_string(s: String):
print(s, end="")
fn print_many[T: Stringable, *Ts: Stringable](first: T, *rest: *Ts):
print_string(String(first))
@parameter
fn print_elt[T: Stringable](a: T):
print_string(" ")
print_string(String(a))
rest.each[print_elt]()
print_many("Bob")
fn print_string(s: String):
print(s, end="")
fn print_many[T: Stringable, *Ts: Stringable](first: T, *rest: *Ts):
print_string(String(first))
@parameter
fn print_elt[T: Stringable](a: T):
print_string(" ")
print_string(String(a))
rest.each[print_elt]()
print_many("Bob")
Bob
Bob
If you call print_many()
with a single argument, it calls print_string()
directly. The VariadicPack
is empty, so each()
returns immediately without
calling the print_elt()
function.
Variadic keyword arguments
Mojo functions also support variadic keyword arguments (**kwargs
). Variadic
keyword arguments allow the user to pass an arbitrary number of keyword
arguments. To define a function that takes a variadic keyword argument, use the
variadic keyword argument syntax **kw_argument_name
:
fn print_nicely(**kwargs: Int) raises:
for key in kwargs.keys():
print(key[], "=", kwargs[key[]])
# prints:
# `a = 7`
# `y = 8`
print_nicely(a=7, y=8)
fn print_nicely(**kwargs: Int) raises:
for key in kwargs.keys():
print(key[], "=", kwargs[key[]])
# prints:
# `a = 7`
# `y = 8`
print_nicely(a=7, y=8)
In this example, the argument name kwargs
is a placeholder that accepts any
number of keyword arguments. Inside the body of the function, you can access
the arguments as a dictionary of keywords and argument values (specifically,
an instance of
OwnedKwargsDict
).
There are currently a few limitations:
-
Variadic keyword arguments are always implicitly treated as if they were declared with the
owned
argument convention, and can't be declared otherwise:# Not supported yet.
fn read_var_kwargs(read **kwargs: Int): ...# Not supported yet.
fn read_var_kwargs(read **kwargs: Int): ... -
All the variadic keyword arguments must have the same type, and this determines the type of the argument dictionary. For example, if the argument is
**kwargs: Float64
then the argument dictionary will be aOwnedKwargsDict[Float64]
. -
The argument type must conform to the
CollectionElement
trait. That is, the type must be bothMovable
andCopyable
. -
Dictionary unpacking is not supported yet:
fn takes_dict(d: Dict[String, Int]):
print_nicely(**d) # Not supported yet.fn takes_dict(d: Dict[String, Int]):
print_nicely(**d) # Not supported yet. -
Variadic keyword parameters are not supported yet:
# Not supported yet.
fn var_kwparams[**kwparams: Int](): ...# Not supported yet.
fn var_kwparams[**kwparams: Int](): ...
Positional-only and keyword-only arguments
When defining a function, you can restrict some arguments so that they can be passed only as positional arguments, or they can be passed only as keyword arguments.
To define positional-only arguments, add a slash character (/
) to the
argument list. Any arguments before the /
are positional-only: they can't be
passed as keyword arguments. For example:
fn min(a: Int, b: Int, /) -> Int:
return a if a < b else b
fn min(a: Int, b: Int, /) -> Int:
return a if a < b else b
This min()
function can be called with min(1, 2)
but can't be called using
keywords, like min(a=1, b=2)
.
There are several reasons you might want to write a function with positional-only arguments:
- The argument names aren't meaningful for the the caller.
- You want the freedom to change the argument names later on without breaking backward compatibility.
For example, in the min()
function, the argument names don't add any real
information, and there's no reason to specify arguments by keyword.
For more information on positional-only arguments, see PEP 570 – Python Positional-Only Parameters.
Keyword-only arguments are the inverse of positional-only arguments: they can be specified only by keyword. If a function accepts variadic arguments, any arguments defined after the variadic arguments are treated as keyword-only. For example:
fn sort(*values: Float64, ascending: Bool = True): ...
fn sort(*values: Float64, ascending: Bool = True): ...
In this example, the user can pass any number of Float64
values, optionally
followed by the keyword ascending
argument:
var a = sort(1.1, 6.5, 4.3, ascending=False)
var a = sort(1.1, 6.5, 4.3, ascending=False)
If the function doesn't accept variadic arguments, you can add a single star
(*
) to the argument list to separate the keyword-only arguments:
fn kw_only_args(a1: Int, a2: Int, *, double: Bool) -> Int:
var product = a1 * a2
if double:
return product * 2
else:
return product
fn kw_only_args(a1: Int, a2: Int, *, double: Bool) -> Int:
var product = a1 * a2
if double:
return product * 2
else:
return product
Keyword-only arguments often have default values, but this is not required. If a keyword-only argument doesn't have a default value, it is a required keyword-only argument. It must be specified, and it must be specified by keyword.
Any required keyword-only arguments must appear in the signature before any optional keyword-only arguments. That is, arguments appear in the following sequence a function signature:
- Required positional arguments.
- Optional positional arguments.
- Variadic arguments.
- Required keyword-only arguments.
- Optional keyword-only arguments.
- Variadic keyword arguments.
For more information on keyword-only arguments, see PEP 3102 – Keyword-Only Arguments.
Overloaded functions
All function declarations must specify argument types, so if you want a want a function to work with different data types, you need to implement separate versions of the function that each specify different argument types. This is called "overloading" a function.
For example, here's an overloaded add()
function that can accept either
Int
or String
types:
fn add(x: Int, y: Int) -> Int:
return x + y
fn add(x: String, y: String) -> String:
return x + y
fn add(x: Int, y: Int) -> Int:
return x + y
fn add(x: String, y: String) -> String:
return x + y
If you pass anything other than Int
or String
to the add()
function,
you'll get a compiler error. That is, unless Int
or String
can implicitly
cast the type into their own type. For example, String
includes an overloaded
version of its constructor (__init__()
) that supports
implicit conversion
from a StringLiteral
value. Thus, you can also pass a StringLiteral
to a
function that expects a String
.
When resolving an overloaded function call, the Mojo compiler tries each candidate function and uses the one that works (if only one version works), or it picks the closest match (if it can determine a close match), or it reports that the call is ambiguous (if it can't figure out which one to pick). For details on how Mojo picks the best candidate, see Overload resolution.
If the compiler can't figure out which function to use, you can resolve the
ambiguity by explicitly casting your value to a supported argument type. For
example, the following code calls the overloaded foo()
function,
but both implementations accept an argument that supports implicit
conversion
from StringLiteral
. So, the call to foo(string)
is ambiguous and creates a
compiler error. You can fix this by casting the value to the type you really
want:
@value
struct MyString:
@implicit
fn __init__(out self, string: StringLiteral):
pass
fn foo(name: String):
print("String")
fn foo(name: MyString):
print("MyString")
fn call_foo():
alias string: StringLiteral = "Hello"
# foo(string) # error: ambiguous call to 'foo' ... This call is ambiguous because two `foo` functions match it
foo(MyString(string))
@value
struct MyString:
@implicit
fn __init__(out self, string: StringLiteral):
pass
fn foo(name: String):
print("String")
fn foo(name: MyString):
print("MyString")
fn call_foo():
alias string: StringLiteral = "Hello"
# foo(string) # error: ambiguous call to 'foo' ... This call is ambiguous because two `foo` functions match it
foo(MyString(string))
Overloading also works with combinations of both fn
and def
function
declarations.
Overload resolution
When resolving an overloaded function, Mojo does not consider the return type or other contextual information at the call site—it considers only parameter and argument types and whether the functions are instance methods or static methods.
The overload resolution logic filters for candidates according to the following rules, in order of precedence:
- Candidates requiring the smallest number of implicit conversions (in both arguments and parameters).
- Candidates without variadic arguments.
- Candidates without variadic parameters.
- Candidates with the shortest parameter signature.
- Non-
@staticmethod
candidates (over@staticmethod
ones, if available).
If there is more than one candidate after applying these rules, the overload resolution fails. For example:
@register_passable("trivial")
struct MyInt:
"""A type that is implicitly convertible to `Int`."""
var value: Int
@implicit
fn __init__(out self, _a: Int):
self.value = _a
fn foo[x: MyInt, a: Int]():
print("foo[x: MyInt, a: Int]()")
fn foo[x: MyInt, y: MyInt]():
print("foo[x: MyInt, y: MyInt]()")
fn bar[a: Int](b: Int):
print("bar[a: Int](b: Int)")
fn bar[a: Int](*b: Int):
print("bar[a: Int](*b: Int)")
fn bar[*a: Int](b: Int):
print("bar[*a: Int](b: Int)")
fn parameter_overloads[a: Int, b: Int, x: MyInt]():
# `foo[x: MyInt, a: Int]()` is called because it requires no implicit
# conversions, whereas `foo[x: MyInt, y: MyInt]()` requires one.
foo[x, a]()
# `bar[a: Int](b: Int)` is called because it does not have variadic
# arguments or parameters.
bar[a](b)
# `bar[*a: Int](b: Int)` is called because it has variadic parameters.
bar[a, a, a](b)
parameter_overloads[1, 2, MyInt(3)]()
struct MyStruct:
fn __init__(out self):
pass
fn foo(mut self):
print("calling instance method")
@staticmethod
fn foo():
print("calling static method")
fn test_static_overload():
var a = MyStruct()
# `foo(mut self)` takes precedence over a static method.
a.foo()
@register_passable("trivial")
struct MyInt:
"""A type that is implicitly convertible to `Int`."""
var value: Int
@implicit
fn __init__(out self, _a: Int):
self.value = _a
fn foo[x: MyInt, a: Int]():
print("foo[x: MyInt, a: Int]()")
fn foo[x: MyInt, y: MyInt]():
print("foo[x: MyInt, y: MyInt]()")
fn bar[a: Int](b: Int):
print("bar[a: Int](b: Int)")
fn bar[a: Int](*b: Int):
print("bar[a: Int](*b: Int)")
fn bar[*a: Int](b: Int):
print("bar[*a: Int](b: Int)")
fn parameter_overloads[a: Int, b: Int, x: MyInt]():
# `foo[x: MyInt, a: Int]()` is called because it requires no implicit
# conversions, whereas `foo[x: MyInt, y: MyInt]()` requires one.
foo[x, a]()
# `bar[a: Int](b: Int)` is called because it does not have variadic
# arguments or parameters.
bar[a](b)
# `bar[*a: Int](b: Int)` is called because it has variadic parameters.
bar[a, a, a](b)
parameter_overloads[1, 2, MyInt(3)]()
struct MyStruct:
fn __init__(out self):
pass
fn foo(mut self):
print("calling instance method")
@staticmethod
fn foo():
print("calling static method")
fn test_static_overload():
var a = MyStruct()
# `foo(mut self)` takes precedence over a static method.
a.foo()
foo[x: MyInt, a: Int]()
bar[a: Int](b: Int)
bar[*a: Int](b: Int)
foo[x: MyInt, a: Int]()
bar[a: Int](b: Int)
bar[*a: Int](b: Int)
Return values
Return value types are declared in the signature using the
-> type
syntax. Values are
passed using the return
keyword, which ends the function and returns the
identified value (if any) to the caller.
def get_greeting() -> String:
return "Hello"
def get_greeting() -> String:
return "Hello"
By default, the value is returned to the caller as an owned value. As with
arguments, a return value may be implicitly
converted to
the named return type. For example, the previous example calls return
with a
string literal, "Hello"
, which is implicitly converted to a String
.
Named results
Named function results allow a function to return a value that can't be moved or
copied. Named result syntax lets you specify a named, uninitialized variable to
return to the caller using the out
argument convention:
def get_name_tag(owned name: String, out name_tag: NameTag):
name_tag = NameTag(name^)
def get_name_tag(owned name: String, out name_tag: NameTag):
name_tag = NameTag(name^)
The out
argument convention identifies an uninitialized variable that the
function must initialize. (This is the same as the out
convention used in
struct constructors.) The out
argument for a named result can appear anywhere in the argument list, but by
convention, it should be the last argument in the list.
A function can declare only one return value, whether it's declared using an
out
argument or using the standard ->
type
syntax.
A function with a named result argument doesn't need to include an explicit
return
statement, as shown above. If the function terminates without a return
,
or at a return
statement with no value, the value of the out
argument is
returned to the caller. If it includes a return
statement with a value, that
value is returned to the caller, as usual.
The fact that a function uses a named result is transparent to the caller. That is, these two signatures are interchangeable to the caller:
def get_name_tag(owned name: String) -> NameTag:
...
def get_name_tag(owned name: String, out name_tag: NameTag):
...
def get_name_tag(owned name: String) -> NameTag:
...
def get_name_tag(owned name: String, out name_tag: NameTag):
...
In both cases, the call looks like this:
tag = get_name_tag("Judith")
tag = get_name_tag("Judith")
Because the return value is assigned to this special out
variable, it doesn't
need to be moved or copied when it's returned to the caller. This means that you
can create a function that returns a type that can't be moved or copied, and
which takes several steps to initialize:
struct ImmovableObject:
var name: String
fn __init__(out self, owned name: String):
self.name = name^
def create_immovable_object(owned name: String, out obj: ImmovableObject):
obj = ImmovableObject(name^)
obj.name += "!"
# obj is implicitly returned
def main():
my_obj = create_immutable_object("Blob")
struct ImmovableObject:
var name: String
fn __init__(out self, owned name: String):
self.name = name^
def create_immovable_object(owned name: String, out obj: ImmovableObject):
obj = ImmovableObject(name^)
obj.name += "!"
# obj is implicitly returned
def main():
my_obj = create_immutable_object("Blob")
By contrast, the following function with a standard return value doesn't work:
def create_immovable_object2(owned name: String) -> ImmovableObject:
obj = ImmovableObject(name^)
obj.name += "!"
return obj^ # Error: ImmovableObject is not copyable or movable
def create_immovable_object2(owned name: String) -> ImmovableObject:
obj = ImmovableObject(name^)
obj.name += "!"
return obj^ # Error: ImmovableObject is not copyable or movable
Because create_immovable_object2
uses a local variable to store the object
while it's under construction, the return call requires it to be either moved
or copied to the callee. This isn't an issue if the newly-created value is
returned immediately:
def create_immovable_object3(owned name: String) -> ImmovableObject:
return ImmovableObject(name^) # OK
def create_immovable_object3(owned name: String) -> ImmovableObject:
return ImmovableObject(name^) # OK
Raising and non-raising functions
By default, when a function raises an error, the function terminates immediately and the error propagates to the calling function. If the calling function doesn't handle the error, it continues to propagate up the call stack.
def raises_error():
raise Error("There was an error.")
def raises_error():
raise Error("There was an error.")
The Mojo compiler always treats a function declared with def
as a raising
function, even if the body of the function doesn't contain any code that could
raise an error.
Functions declared with fn
without the raises
keyword are non-raising
functions—that is, they are not allowed to propagate an error to the calling
function. If a non-raising function calls a raising function, it must handle
any possible errors.
# This function will not compile
fn unhandled_error():
raises_error() # Error: can't call raising function in a non-raising context
# Explicitly handle the error
fn handle_error():
try:
raises_error()
except e:
print("Handled an error:", e)
# Explicitly propagate the error
fn propagate_error() raises:
raises_error()
# This function will not compile
fn unhandled_error():
raises_error() # Error: can't call raising function in a non-raising context
# Explicitly handle the error
fn handle_error():
try:
raises_error()
except e:
print("Handled an error:", e)
# Explicitly propagate the error
fn propagate_error() raises:
raises_error()
If you're writing code that you expect to use widely or distribute as a package,
you may want to use fn
functions for APIs that don't raise errors to limit
the number of places users need to add unnecessary error handling code. For some
extremely performance-sensitive code, it may be preferable to avoid run-time
error-handling.
For more information, see Errors, error handling, and context managers.
Was this page helpful?
Thank you! We'll create more content like this.
Thank you for helping us improve!