What are Packages and Collections?

In Odin we have the concept of packages. A package is a directory that contains different files. Directories are important to Odin. Package names must be unique and be a valid identifier. Example My_Package.

package/
- file1.odin
- file2.odin

Is like the compiler concatenates all the files in a single file for a package.

Similar to this:

$ cat cat/paws.odin cat/meows.odin > cat/amalgamated_package.odin

All the files in the same package will share the package name in the file.

Example

  • cat/paws.odin: package Cat.
  • cat/meows.odin: package Cat.

For using the public procedures, constants, structs, unions from a package we have to import it. Keep in mind that we import the directory as a whole and not a single file.

import "lib/cat"

We can make an alias by using another name before the route.

import Cat "../cat"
Cat.meow()
Cat.sleep()

Aliases are needed if your package has an invalid identifier. For example if you store a library inside a version number.

0.4/
  - mylib.odin

If we would like to import it we need an alias.

import mylib "0.4"

Collections

Odin has the concept of collections that are predefined paths that can be used in imports.

  • core: The most common collection that contains useful libraries from Odin core like fmt or strings.

You can define your own collection at build time

The following will define the collection project and put the path at the current directory.

$ odin run . -collection:project=.
import "project:lib/cat"

Package Exports

Odin Exported Names are public by default (anything you declare in a file is public and can be accesed by other packages). If you don’t want to export something you have to use @(private) before declaration.

@(private)
my_variable: int // cannot be accessed outside this package

@(private) is equivalent to @(private="package").

You can make the declarations private by file, only available within the file where was declared.

@(private="file")
my_variable: int // cannot be accessed outside this file

Another option is using the #+private or #+private file general attributes before the package name. That makes all the file private by default and only available to the package or file itself.

private_by_default.odin

#+private
package Package1

// This procedure will only be available to the package
// impossible to make it public unless is stored in another file
// without the #+private directive
private_proc :: proc() -> int {
  return 42
}

By using this alternative we can have a file that is only for storing private procedures and other data structures.

Being private by default is a benefit because it requires you to explicitly expose things to be part of your libraries API, improving the organization of your package.

my_package/
  - api.odin // Public exports
  - api.priv.odin // Package only exports

We can store all the private declarations in a single private file and not clutter with @(private) in every symbol.

Package naming convention

The files and directory inside the package can be named with any valid operating system identifier name. And the package name could be different from the directory name. There is no official convention or standard for naming packages and files. The only requirement is that package names must be unique and be a valid Odin identifier.

Howerer these are some recommendations.

Directory Names

Use CamelCase for directory names. This way our packages can be seen as “modules” that exports procedures, constants, structs and unions.

import "lib/Cat"
Cat.meow()

A package can internally use multiple packages and procedures, but we never interact with those internal packages directly. We name the directory Cat because it exposes and groups all of the feline functionality.

By giving packages that expose and group related functionality CamelCase naming, we help developers identify these patterns.

Package Identifier

Use Ada_Case for package identifiers. Package identifiers must be a valid Odin identifier and be unique within the project. They do not need to follow the directory structure.

Example

Our Cat package can be stored in lib/animals/Cat but we don’t need to follow the directory tree for naming the package, as long as is unique it would be fine.

package Cat

Note: The core Odin library does use another convention using snake_case (preferred a single word) for package names. Use the convention that most fits your taste and needs.

The idea is to be consistent in our project

Files

Files can be named with any operating system valid identifier.
Here are some recommendations:

  • use lowercase for file names.
  • the main file must be named as the directory in lowercase. Eg. animals/Cat/cat.odin
  • use dot (.) for separating exports scopes. Eg. api.odin, api.priv.odin.
  • use underscore (_) for separating long filenames. my_great_api.odin
  • functional postfixes also use underscore only_linux.odin, only_amd64.odin. For procedures and other symbols that are exclusive to that target. The naming convention would be only_<target>. Example: api.priv.only_linux.odin
8 Likes

Just wanted to say thanks, these posts are awesome.

1 Like