Case Conversion
Oxide uses camelCase for functions, variables, and fields as its idiomatic style, while Rust conventions prefer snake_case. The case conversion feature automatically transforms Oxide's camelCase identifiers to snake_case in the generated Rust code.
Case conversion is always enabled in Oxide.
Motivation
Writing idiomatic Oxide code means using camelCase:
fn loadUserData(userId: Int): User {
let userName = fetchName(userId)
let userData = User(
firstName: userName.first,
lastName: userName.last
)
userData
}
When this transpiles to Rust, the generated code follows Rust's snake_case conventions:
fn load_user_data(user_id: isize) -> User {
let user_name = fetch_name(user_id);
let user_data = User {
first_name: user_name.first,
last_name: user_name.last,
};
user_data
}
Rust code can now call Oxide functions naturally:
// From Rust, call the Oxide function using snake_case
let user = load_user_data(42);
What Gets Converted
| Item Type | Oxide Style | Rust Output |
|---|---|---|
| Functions | loadUserData | load_user_data |
| Variables | userName | user_name |
| Struct fields | firstName | first_name |
| Field access | user.firstName | user.first_name |
| Method calls | user.loadData() | user.load_data() |
| Module names | mod userAuth | mod user_auth |
| Import paths | import auth.loadUser | use auth::load_user |
| Statics | static myCounter | static my_counter |
| Labels | 'outerLoop | 'outer_loop |
| Closures | { userName: String -> ... } | |user_name: String| ... |
What Stays Unchanged
Certain identifiers are never converted to preserve correctness:
Type Names (PascalCase)
struct UserProfile(name: String) // UserProfile stays UserProfile
enum Status { case Active } // Status and Active unchanged
Generic Type Parameters
fn identity<T>(value: T): T { value } // T stays T
Constants (SCREAMING_SNAKE_CASE)
const MAX_SIZE: Int = 100 // MAX_SIZE stays MAX_SIZE
Extern Blocks (FFI Compatibility)
Names in extern blocks must match their C/FFI counterparts exactly:
extern "C" {
fn myCallback(x: Int): Int // myCallback stays myCallback
fn initLibrary(): Bool // initLibrary stays initLibrary
}
Items with @[preserveName]
Use the @[preserveName] attribute to opt-out specific items:
@[preserveName]
fn camelCaseApi(): Int { 42 } // Keeps camelCaseApi in generated Rust
Serde-Annotated Structs
Struct fields with Serialize or Deserialize derives keep their original names to preserve JSON API compatibility:
@[derive(Serialize, Deserialize)]
struct ApiResponse(
userId: Int, // Field name stays userId in JSON
firstName: String // Field name stays firstName in JSON
)
Without serde derives, fields are converted normally:
struct InternalData(
userId: Int, // Becomes user_id
firstName: String // Becomes first_name
)
Collision Detection
When two different names convert to the same snake_case result, the transpiler reports an error:
fn fooBar(): Int { 1 }
fn foo_bar(): Int { 2 } // Error: Both convert to foo_bar
Error message:
error: Name collision detected
--> src/lib.ox:2:1
|
1 | fn fooBar(): Int { 1 }
| ------ first defined here (converts to 'foo_bar')
2 | fn foo_bar(): Int { 2 }
| ^^^^^^^^ 'foo_bar' conflicts with 'fooBar' (both convert to 'foo_bar')
|
= help: rename one of these functions to avoid the collision
Collision detection happens per-file and per-scope. Cross-file collisions are caught by the Rust compiler during the subsequent build step.
Acronyms and Edge Cases
The conversion algorithm handles common edge cases:
| Input | Output | Notes |
|---|---|---|
parseJSON | parse_json | Acronyms treated as single word |
HTTPSClient | https_client | Consecutive uppercase → lowercase |
user1Name | user1_name | Numbers stay with preceding word |
_unusedVar | _unused_var | Leading underscore preserved |
user_name | user_name | Already snake_case, unchanged |
LoadUser | load_user | PascalCase functions converted |
Error Messages
Error messages from the Rust compiler are transformed to show Oxide-style camelCase names, making it easier to locate issues in your source code.
Rust compiler output:
error[E0425]: cannot find value `user_name` in this scope
Displayed as:
error[E0425]: cannot find value `userName` in this scope
Generated Code
The generated Rust code:
- Uses snake_case for all converted identifiers
- Omits
# - Follows Rust naming conventions for clippy and rustfmt compatibility
Best Practices
-
Use
@[preserveName]sparingly: Only for FFI callbacks or external API requirements. -
Review serde structs: Understand that serde fields keep original names for JSON compatibility. Use
@[serde(rename = "...")]if you need different behavior. -
Avoid manual snake_case in Oxide: Write idiomatic camelCase and let the conversion handle the rest. Names like
my_functionin Oxide source are valid but not idiomatic.
See Also
- Attributes - Using
@[preserveName] - Modules - Module naming and organization
- Comparison - Oxide vs Rust syntax