Odin's User Format
Odin's User Format

Odin has a pattern that allows you to create your own formats for use in the printf functions through out odin. With them you will be able to create your own way of formatting your data and be able to print them using standard functions.

To demonstrate this, this paper will take a simple example. If you have an integer print that integer with the caption of "This is the integer: 2". You will be able to do this with printf("%t", 2)

Creating the custom formatter.

The Formatter type

// Custom formatter signature. It returns true if the formatting was successful
// and false when it could not be done
User_Formatter :: #type proc(fi: ^Info, arg: any, verb: rune) -> bool

Parameters for a User_Formatter

  • fi

    fi is a pointer to fmt.Info. Info looks like this.

    // Internal data structure that stores the required information for formatted printing
    Info :: struct {
        using state: Info_State,
    
        writer: io.Writer,
        arg: any, // Temporary
        indirection_level: int,
        record_level: int,
    
        optional_len: Maybe(int),
        use_nul_termination: bool,
    
        n: int, // bytes written
    }
    
    Info_State :: struct {
        minus:     bool,
        plus:      bool,
        space:     bool,
        zero:      bool,
        hash:      bool,
        width_set: bool,
        prec_set:  bool,
    
        ignore_user_formatters: bool,
        in_bad: bool,
    
        width:     int,
        prec:      int,
        indent:    int,
    
        parent_struct: any,
    }
    

    I won't go into what each field is for. However there are a couple of very important fields

    • writer

      This is where you should send any formatting you will do.

    • state.ignore_user__formatters

      This will turn the user formatters on or off. Needed when you want the default formatter

  • arg

    This is the actual data. Because it is an any type you can access the data through arg.data which is a rawptr to the data. If you need to access the type it is present in arg.type

  • verb

    This is a rune that is part of the format string. ex "%s" the verb will be 's'

Example Formatter

// Examplt formatter
prefix_integer_formatter::proc(fi: ^Info, arg: any, verb: rune) -> bool {
  data: ^int = cast(^int)arg.data

  switch verb {
  case 'p':
    fi.ignore_user_formatters = true
    wprintf(fi.writer, "This is the integer: %i", data^)
    fi.ignore_user_formatters = false
  case:
    fi.ignore_user_formatters = true
    fmt.fmt_value(fi, arg, verb)
    fi.ignore_user_formatters = false
  }

  return true
}

About the Example Formatter

  • If you are calling wprintf or any other fmt functions, be sure to set fi.ignore_users_formatters to true. This prevents an infinite loop since if it is false it will keep calling your custom formatter.
  • if you change fi.ignore_users_formatters set it back to its original value. Each formatter call for a wprintf uses the same fi so if you want your custom formatters to continue to be called ignore_users_formatters must be set to false

Using the prefix_integer_formatter

Registering the formatter

When registering a formatter you must first create an map where the formatters will be listed. This only needs to be done once. In fact ODIN will prevent you from creating more then one.

user_formatters := make(map[typeid]fmt.User_Formatter)
fmt.set_user_formatters(&user_formatters) // can only be called once per program

Once the map has been set you can then register your formatters

err := fmt.register_user_formatter(int, prefix_integer_formatter)

Using the new formatter.

Using the new formatter is as simple as using the new verb

printf("%p", 2)

Conclusion

User Formatters give you another way to do some complex formatting and encapsulate it into a single function. They are fun to write and a nice way to add test your ODIN skills.

Appendix

Adding C's way of truncating strings from printf

ODIN does not support using %.5s to truncate the string to 5 characters. This is a standard in C, Go, and Java. I wrote a custom formatter to add this functionality.

package formatters

import "core:fmt"
import "core:strings"

@(private = "file")
STRING_VERB :: 's'

// formatter intended for strings.  Odin does not support using precision in strings
// to specify the maximum length of the string.  This will fix that problem.
truncate_string :: proc(fi: ^fmt.Info, arg: any, verb: rune) -> bool {
    data: ^string = cast(^string)arg.data

    new_string := format_string(fi, data^)
    fmt.fmt_string(fi, new_string, verb)
    return true
}

// Do the actual truncation if the precision has been set.
// params:
//    fi: Pointer to the format info
//    data: The string to work with
// returns:
//    The newly formatted string
@(private = "file")
format_string :: proc(fi: ^fmt.Info, data: string) -> (result: string) {
    result = data

    if fi.prec_set {
        result, _ = strings.substring_to(data, clamp(fi.prec, 0, strings.rune_count(data)))
    }

    return
}

Just presented as another user formatter that may come in useful.

Author: Bruce Haugland

Created: 2026-06-20 Sat 20:24

Validate