This document serves as a comprehensive guide for translating raylib C examples to Nim using the naylib wrapper. It details the patterns, idioms, and conventions used in the existing codebase to ensure consistency and maintainability.
Every example follows a consistent structure:
# ****************************************************************************************
#
# raylib [core] example - Basic window
#
# Example complexity rating: [★☆☆☆] 1/4
#
# Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
# BSD-like license that allows static linking with closed source software
#
# Copyright (c) 2013-2025 Ramon Santamaria (@raysan5)
#
# ****************************************************************************************
import raylib, std/[math, lenientops]
const
ScreenWidth = 800
ScreenHeight = 450
proc main =
# Initialization
initWindow(ScreenWidth, ScreenHeight, "raylib [core] example - basic window")
defer: closeWindow() # Important pattern in naylib
setTargetFPS(60)
# Main game loop
while not windowShouldClose():
# Update
# TODO: Update variables here
# Draw
drawing(): # Using template for safer begin-end pairs
clearBackground(RayWhite)
drawText("Congrats! You created your first window!", 190, 200, 20, LightGray)
main()- Standard Header Comment: Always include the full original raylib header, adapted to Nim's comment syntax
- Import Section: Include only the necessary naylib (
raylib,raymath, etc.) and Nim standard library modules used in the code - Nim Alternatives: Use Nim standard library or packages for text, file, compression, and encoding utilities.
- Function Overloading: Remove any raylib suffixes (
V,Ex,Rec,Pro, etc.) and rely on Nim's overload resolution. - Constants Section: Screen dimensions and other constants at the top
- Main Procedure: All logic encapsulated in a
main()procedure - Initialization Block: Window initialization with
deferfor cleanup - Game Loop: Standard while loop with update/draw sections
- Resource Management: Rely on automatic destructors for naylib objects like
TextureorFont
- Use
camelCasefor variables and procedure names:var framesCounter: int32 = 0 proc updateCamera() = ...
- Use
PascalCasefor constants:const MaxFrameSpeed = 15 MinFrameSpeed = 1 ScreenWidth = 800
- Use
PascalCasefor type names:type LightKind = enum Directional, Point, Spot
C numeric types are mapped to Nim types following these patterns:
# C: float -> Nim: float32
let posX: float32 = 22.5 # Explicit type required
# C: int -> Nim: int32
let counter: int32 = 0 # Explicit type required
# C: unsigned int -> Nim: uint32
let flags: uint32 = 0 # Explicit type requiredNim Defaults:
- Float literals like
2.0are float64. Integer literals are polymorphic. float64andfloat32are implicitly convertible both ways; int literals convert to many numeric types.
Rules:
-
No suffixes needed for simple literals If you specify the type (e.g.,
: float32), don't add'f32or similar suffixes — they are redundant. -
Use whole numbers when possible Write whole numbers without a decimal point (e.g.,
45instead of45.0), even if the target type is a float. -
Prevent unintended float widening or mismatches Use literal suffixes (
'f32) or explicit conversion (float32(value)), (e.g.,lineThick*0.5'f32,float32(screenWidth)/2) to ensure float32 precision. -
Avoid C-Style Suffixes Do not use C-style suffixes like
0.0f.
Good:
let a: float32 = 15 # No suffix needed
let b: float32 = 0.2 # No suffix needed
let angle: float32 = 180 # Whole number for float type
let angle = degToRad(170'f32) # Add type hints when the type is ambiguous
let result1 = lineThick*0.5'f32 # This prevents unintended float widening
let result2 = screenWidth/2'f32 # This prevents unintended float widening
let result3 = float32(screenWidth)/2 # This prevents unintended float widening
let ratio1: float32 = float32(intValue1) / intValue2 # Convert at least one operand to float32
let ratio2: float32 = float32(intValue1) / float32(intValue2)Bad:
let a: float32 = 15'f32 # Redundant suffix
let b: float32 = 0.2'f32 # Redundant suffix
let angle: float32 = degToRad(170) # May fail due to missing type hint
let result1 = lineThick*0.5 # Unintended float widening to 64-bit
let result3 = screenWidth/2 # The type is inferred as float (64-bit)
let result3 = screenWidth/2.0 # Explicit decimal creates 64-bit float
let ratio: float32 = intValue1 / intValue2 # Expression evaluates to float64, gets down-converted| C Type | Nim Type | Notes |
|---|---|---|
int |
int32 |
Explicitly use 32-bit integers |
short |
int16 |
The standard for 16-bit integers |
long long |
int64 |
The standard for 64-bit integers |
unsigned int |
uint32 |
For non-negative 32-bit integers |
float |
float32 |
Explicitly use 32-bit floats |
double |
float or float64 |
Nim's default float is a 64-bit double |
bool |
bool |
Direct translation |
char*, const char* |
string |
Replace C strings with Nim's safe, managed strings. |
void* (generic data) |
generics |
Use type-safe generics instead of raw pointers. |
struct T |
type T = object |
Direct translation to Nim's object type. |
enum E |
type E = enum |
Direct translation to Nim's safer enum type. |
T* (heap object) |
ref T |
For pointers to shared, managed objects. |
T* (out-parameter) |
var T |
For modifying values in-place (pass-by-reference). |
T* (array) |
seq[T] |
Replace C-style arrays with Nim's dynamic sequences. |
T arr[N] (fixed array) |
array[N, T] |
Use for fixed-size, stack-allocated arrays. |
# Good: This is the standard, clear way to create objects in Nim.
var camera = Camera(
position: Vector3(x: 5, y: 5, z: 5),
target: Vector3(x: 0, y: 0, z: 0),
up: Vector3(x: 0, y: 1, z: 0),
fovy: 45,
projection: Perspective
)
# Also works, but is less ideal:
var camera: Camera
camera.position = Vector3(x: 5, y: 5, z: 5)
camera.target = Vector3(x: 0, y: 0, z: 0)
camera.up = Vector3(x: 0, y: 1, z: 0)
camera.fovy = 45
camera.projection = PerspectiveTranslate raylib C functions to Nim by removing any raylib suffix (V, Ex, Rec, Pro, etc.) if present; leave functions without suffixes unchanged. Leaving a suffix on an overloaded function will produce incorrect Nim code.
InitWindow(screenWidth, screenHeight, "Title");
DrawSphereEx(centerPos, radius, rings, slices, RED);
DrawRectanglePro(sourceRec, destRec, origin, 45.0f, GREEN);
DrawCircleLinesV(center, 30, BLUE);
DrawTextureRec(texture, sourceRec, position, WHITE);initWindow(screenWidth, screenHeight, "Title") # Unchanged, no suffix to remove
drawSphere(centerPos, radius, rings, slices, Red) # Removed the "Ex" suffix
drawRectangle(sourceRec, destRec, origin, 45'f32, Green) # Removed the "Pro" suffix
drawCircleLines(center, 30, Blue) # Removed the "V" suffix
drawTexture(texture, sourceRec, position, White) # Removed the "Rec" suffixFor raylib functions not wrapped in naylib (text handling, file I/O, compression, encoding, etc.), prefer Nim standard library functions or recommended external packages. A complete reference is available in https://github.com/planetis-m/naylib/blob/main/manual/alternatives_table.rst.
GetRandomValue(0, 10);
float angle = DEG2RAD * 90.0f;
if (FileExists("data.txt")) { ... }
int n = TextLength("hello");let value = rand(0..10)
let angle = degToRad(90'f32)
if fileExists("data.txt"): ...
let n = "hello".lenUse direct translations of input functions with camelCase naming and Nim's boolean expressions:
if (IsKeyPressed(KEY_SPACE))
// Handle space key press
if (IsKeyDown(KEY_LEFT))
// Handle left key being held down
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT))
// Handle left mouse button pressif isKeyPressed(Space):
# Handle space key press
if isKeyDown(Left):
# Handle left key being held down
if isMouseButtonPressed(Left):
# Handle left mouse button pressUse templates for scoped operations:
drawing(): # Equivalent to beginDrawing()/endDrawing()
clearBackground(RayWhite)
# Drawing code here
mode3D(camera): # Equivalent to beginMode3D()/endMode3D()
drawModel(model, position, scale, White)Naylib uses destructors for automatic memory management of types like Image, Wave, Texture, etc. This eliminates the need for manual Unload calls:
let texture = loadTexture("image.png")
# No need to manually unload - automatically cleaned up by destructorFor cases where you want explicit control or need to ensure cleanup at a specific point:
var image = loadImage("resources/heightmap.png") # Load image (RAM)
let texture = loadTextureFromImage(image) # Convert image to texture (VRAM)
reset(image) # Unload image from RAM, already uploaded to VRAMWhen translating raymath functions from C to Nim, use operators where available and drop the type prefixes from function names.
Vector3 sum = Vector3Add(a, b);
Vector3 diff = Vector3Subtract(a, b);
Vector3 scale = Vector3Scale(a, factor);
Vector3 mul = Vector3Multiply(a, b);
Vector3 div = Vector3Divide(a, b);
Vector3 trans = Vector3Transform(a, matrix);
float dist = Vector3Distance(a, b);
Vector3 neg = Vector3Negate(a);
Quaternion q = QuaternionMultiply(q1, q2);
if (Vector3Equals(a, b));let sum = a + b
let diff = a - b
let scale = a*factor
let mul = a*b
let `div` = a/b
let trans = a*matrix
let dist = distance(a, b)
let neg = -a
let q = q1*q2
if a =~ b: discard # approximate equalityImport std/lenientops to allow direct arithmetic between ints and floats avoiding repetitive type conversions.
import std/lenientops
var
count: int32 = 10
scaleFactor: float32 = 3.5
offset: int32 = 100
adjustment: float32 = 50.5
# Without lenientops, you'd need explicit casts:
let result1 = float32(count) * scaleFactor
let result2 = offset + int32(adjustment)
# With lenientops, direct operations work:
let result1 = count * scaleFactor # Works directly
let result2 = offset + adjustment # Works directlyFollowing raylib's coding style, omit spaces around * and /, but include spaces around + and -.
# Good raylib style
let centerX = screenWidth/2'f32 - buttonWidth/2'f32
let centerY = screenHeight/2'f32 - buttonHeight/2'f32
let scaledValue = baseValue*1.5'f32
# Less preferred
let centerX = screenWidth / 2'f32 - buttonWidth / 2'f32
let centerY = screenHeight / 2'f32 - buttonHeight / 2'f32
let scaledValue = baseValue * 1.5'f32- Break lines only after binary operators, commas, or open parentheses.
- Place binary operators at the end of the line, not at the start of the next line.
- Indent continuation lines consistently.
- Unary operators (e.g., a leading
-for a negative term) may appear at the start of a line.
# Incorrect (causes errors):
let a1 = (-G*(2*m1 + m2)*sin(theta1)
- m2*G*sin(theta1 - 2*theta2)
- 2*sinD*m2*(ww2*L2 + ww1*L1*cosD))
/ (L1*(2*m1 + m2 - m2*cos2D))
# Correct:
let a1 = (-G*(2*m1 + m2)*sin(theta1) -
m2*G*sin(theta1 - 2*theta2) -
2*sinD*m2*(ww2*L2 + ww1*L1*cosD)) /
(L1*(2*m1 + m2 - m2*cos2D))Naylib's load* functions automatically validate asset loading and raise RaylibError if they fail:
# This will automatically raise RaylibError if loading fails
let texture = loadTexture("image.png")
# We skip explicit error handling in the examples for brevity.Use assertions for preconditions:
import std/assertions
assert(windowIsReady(), "Window should be initialized")Use Nim string interpolation:
DrawText(TextFormat("TARGET FPS: %i", targetFPS), x, y, fontSize, color);import std/strformat
drawText(&"TARGET FPS: {targetFPS}", x, y, fontSize, color)initAudioDevice()
defer: closeAudioDevice() # Still needed as it's a global resourcewhen defined(GraphicsApiOpenGl33):
const GlslVersion = 330
else:
const GlslVersion = 100
let fragShaderFileName = &"resources/shaders/glsl{GlslVersion}/reload.fs"int colorLoc = GetShaderLocation(shader, "color");
float color[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
SetShaderValue(shader, colorLoc, color, SHADER_UNIFORM_VEC4); // must pass the uniform type explicitlylet colorLoc = getShaderLocation(shader, "color")
let color: array[4, float32] = [1, 0, 0, 1]
setShaderValue(shader, colorLoc, color) # uniform type inferred as Vec4setShaderValue infers the uniform type from the Nim value at compile time (e.g., float32, array[3, float32], array[4, int32]) and forwards it to the low-level implementation, so you don’t need to pass the uniform type explicitly.
Use the flags procedure to work with bitflags like ConfigFlags:
SetConfigFlags(FLAG_MSAA_4X_HINT | FLAG_WINDOW_HIGHDPI)setConfigFlags(flags(Msaa4xHint, WindowHighdpi))