Why can't I use a function in a chapter even though I imported it at the start of my main file?

You need to define (in this case, import) my-func() in the file you want to use it, i.e. chapter.typ. Imports don’t “carry over” from the files including them.

If you have many imports that you don’t want to repeat in many files, you can bundle them in a single file, like this:

// prelude.typ
#import "@preview/package:0.1.0"
#import "funcs.typ": my-func

 

// main.typ
#import "prelude.typ": *

#package.some-function()

#include "chapter.typ"

 

// chapter.typ
#import "prelude.typ": *

#my-func()

The name prelude is just a suggestion; it comes from programming languages that use that name for their set of “default” imports that are automatically available.


Explanation: If you’re used to #include from C, it may come as a surprise that Typst’s import and include features work fairly differently. With C-style includes, the code from chapter.typ would be effectively “copy/pasted” into your main file and, because my-func() is available in the main file, it would also work in the included chapter.

Typst’s name resolution instead uses so-called lexical scoping: variables (including functions) are only available if they’re defined in the surrounding code. Take this example:

#let greeting = "Hello"

#let greet() = {
  greeting + " World"
}

#greet()

Here, the variable greeting can be used in greet() because the functions’s body is surrounded by the rest of the file, where the variable is defined. Calling greet() works because the code at the same “level” and before the code also counts.

The file itself is not “surrounded” by anything*, so a function such as my-func() would not be available; you can’t do anything from outside to change that.

*The exception is that built-in modules and names such as math or heading are always available; they can be thought of as being “defined in surrounding code” and form the proper prelude that Typst code has access to.

2 Likes