BoundaryML/baml · error

library handle is nil when looking up symbol

Error message

library handle is nil when looking up symbol '%s'

What it means

getSymbol guards against looking up a symbol (function) in a library whose dlopen handle is nil. This is an internal invariant check: it means loadLibrary failed but a nil handle was still passed to symbol lookup, or handle propagation lost the error.

Solutions

  1. Check and handle the error returned by loadLibrary before calling getSymbol
  2. Verify the library actually loaded (non-nil handle) before any symbol resolution
  3. Fix initialization flow so that a failed load short-circuits downstream symbol lookups
  4. Log the original dlopen error to identify why the handle was nil

Example fix

// before
handle, _ := loadLibrary(path)
sym, err := getSymbol(handle, "baml_init") // handle is nil
// after
handle, err := loadLibrary(path)
if err != nil {
    return fmt.Errorf("library load failed: %w", err)
}
sym, err := getSymbol(handle, "baml_init")
Defensive patterns

Strategy: type-guard

Validate before calling

handle, err := loadLibrary(path)
if err != nil || handle == nil {
    return fmt.Errorf("library unavailable: %w", err)
}

Type guard

func handleValid(h unsafe.Pointer) bool { return h != nil }

Try / catch

sym, err := getSymbol(handle, name)
if err != nil && strings.Contains(err.Error(), "library handle is nil") {
    // reload the library before retrying symbol lookup
    handle, rerr := loadLibrary(path)
    if rerr != nil { return rerr }
    sym, err = getSymbol(handle, name)
}

Prevention

When it happens

Trigger: Calling getSymbol(handle, name) when handle == nil — i.e., proceeding with symbol resolution after a failed dlopen or with an uninitialized handle.

Common situations: Caller code ignoring the loadLibrary error and continuing to resolve symbols; initialization ordering bugs where the library failed to load at startup but symbol lookups still run.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/9b56ba2e23341edd. Report an issue: GitHub.

Appendix: source

Thrown at engine/language_client_go/baml_go/lib_unix.go:50

			dlopenErr = fmt.Errorf("%w (architecture mismatch)", dlopenErr)
		} else if strings.Contains(dlErrStr, "cannot open shared object file") {
			if strings.Contains(dlErrStr, "Permission denied") {
				dlopenErr = fmt.Errorf("%w (permission denied)", dlopenErr)
			} else {
				dlopenErr = fmt.Errorf("%w (file not found or inaccessible)", dlopenErr)
			}
		} else if strings.Contains(dlErrStr, "image not found") || strings.Contains(dlErrStr, "no such file or directory") {
			dlopenErr = fmt.Errorf("%w (library or dependency not found)", dlopenErr)
		}
		return nil, dlopenErr
	}
	return handle, nil
}

// getSymbol retrieves a symbol from the loaded library
func getSymbol(handle unsafe.Pointer, name string) (unsafe.Pointer, error) {
	if handle == nil {
		return nil, fmt.Errorf("library handle is nil when looking up symbol '%s'", name)
	}

	cName := C.CString(name)
	defer C.free(unsafe.Pointer(cName))

	C.dlerror() // Clear any existing error
	symbol := C.dlsym(handle, cName)
	errStr := C.GoString(C.dlerror())

	if symbol == nil {
		errMsg := fmt.Sprintf("dlsym error for %s", name)
		if errStr != "" {
			errMsg += fmt.Sprintf(": %s", errStr)
		} else {
			errMsg += ": symbol not found"
		}
		return nil, fmt.Errorf("%s", errMsg)
	}

View on GitHub (pinned to bd85ce9dee)