📘 This is a quick reference for syntax lookup. For complete documentation with examples, migration guides, and detailed explanations, see annotations.md.
Quick syntax reference for all annotation types supported by RustyCpp.
RustyCpp supports three main categories of annotations:
- In-Place Annotations - Written directly in your C++ source code
- External Annotations - For third-party code you can't modify
- STL Annotations - Pre-configured for C++ Standard Library
// Three states: @safe, @unsafe, and undeclared (default)
// Mark next element as safe (full borrow checking + strict calling rules)
// @safe
void myFunction() {
// Can call @safe and @unsafe functions
// CANNOT call undeclared functions
// Cannot do pointer operations
}
// Mark next element as unsafe (skip checking, explicitly documented)
// @unsafe
void lowLevelFunction() {
// Can call anything
// Can do pointer operations
}
// No annotation = undeclared (unaudited legacy code)
void legacyFunction() {
// Not checked, can call anything
// Safe functions CANNOT call this
}
// Apply to namespace
// @safe
namespace myapp {
// All functions here are safe
}
// Header-to-implementation propagation
// In header: @safe void func();
// In .cpp: void func() { /* automatically safe */ }// Basic syntax
// @lifetime: (parameters) -> return_type where constraints
// Examples:
// Borrowing - return has same lifetime as parameter
// @lifetime: (&'a) -> &'a
const T& borrow(const Container& c);
// Ownership transfer
// @lifetime: owned
std::unique_ptr<T> create();
// Mutable borrow
// @lifetime: (&'a mut) -> void
void modify(Data& data);
// Multiple parameters with constraints
// @lifetime: (&'a, &'b) -> &'a where 'a: 'b
const T& selectFirst(const T& a, const T& b);
// Static lifetime
// @lifetime: () -> &'static
const Config& getGlobalConfig();// Combine safety and lifetime
// @safe
// @lifetime: (&'a) -> &'a
const Data& process(const Data& input) {
// Function is both safe and has lifetime checking
}// @external_safety: {
// third_party::func1: safe
// third_party::func2: unsafe
// legacy::old_api: unsafe
// }// @external_lifetime: {
// strchr: (const char* str, int c) -> const char* where str: 'a, return: 'a
// strdup: (const char* str) -> owned char*
// }IMPORTANT: All external functions must be marked [unsafe] because RustyCpp doesn't analyze external code.
// Compact syntax
// @external: {
// function_name: [unsafe, lifetime_spec]
// }
// Examples:
// @external: {
// malloc: [unsafe, (size_t) -> owned void*]
// strlen: [unsafe, (const char*) -> size_t]
// strcpy: [unsafe, (char* dest, const char* src) -> char* where dest: 'a, return: 'a]
// }
// Note: The detailed syntax below is deprecated and not fully implemented
// @external_function: function_name {
// safety: unsafe // Only 'unsafe' is allowed for external functions
// lifetime: (params) -> return
// where: constraints
// }// Whitelist patterns (mark as safe)
// @external_whitelist: {
// patterns: ["std::*", "*::size", "*::empty"]
// }
// Blacklist patterns (mark as unsafe)
// @external_blacklist: {
// patterns: ["*::malloc", "*::free", "*::operator new*"]
// }// Define reusable profile
// @external_profile: profile_name {
// safe: ["pattern1", "pattern2"]
// unsafe: ["pattern3", "pattern4"]
// }
// With annotations
// @external_profile: boost {
// annotations: {
// boost::format: [unsafe, (const string&) -> owned format]
// boost::shared_ptr::get: [unsafe, () -> T* where this: 'a, return: 'a]
// }
// }// @type_lifetime: std::vector<T> {
// at(size_t) -> &'self mut
// at(size_t) const -> &'self
// push_back(T) -> owned
// data() -> *mut
// begin() -> &'self mut
// iterator: &'self mut
// }#include <stl_lifetimes.hpp>
#include <vector>
// @safe
void example() {
std::vector<int> vec = {1, 2, 3};
int& ref = vec[0]; // Tracked: &'vec mut
vec.push_back(4); // ERROR: would invalidate ref
}| Lifetime Type | Meaning | Example |
|---|---|---|
owned |
Transfers ownership | malloc() -> owned void* |
&'a |
Immutable borrow with lifetime 'a | get() -> &'a |
&'a mut |
Mutable borrow with lifetime 'a | get_mut() -> &'a mut |
'static |
Lives forever | global() -> &'static |
*const |
Const raw pointer (unsafe) | data() const -> *const |
*mut |
Mutable raw pointer (unsafe) | data() -> *mut |
'a |
Named lifetime parameter | Used in constraints |
| Constraint | Meaning | Example |
|---|---|---|
'a: 'b |
'a outlives 'b | where 'a: 'b |
param: 'a |
Parameter has lifetime 'a | where str: 'a |
return: 'a |
Return has lifetime 'a | where return: 'a |
'a: 'b: 'c |
Transitive outlives | where 'a: 'b, 'b: 'c |
// Return borrows from parameter
// @lifetime: (&'a) -> &'a
const T& get(const Container& c);// Creates new owned object
// @lifetime: owned
std::unique_ptr<T> make();// Takes reference, returns owned
// @lifetime: (&'a) -> owned
std::string toString(const Data& d);// Selects one of the inputs
// @lifetime: (&'a, &'b) -> &'a where 'a: 'b
const T& choose(const T& first, const T& second);// Modifies in place
// @lifetime: (&'a mut) -> void
void update(Data& data);When multiple annotations could apply, priority is:
- In-place annotations (highest priority)
- External function-specific annotations
- External pattern matches
- Profile annotations
- Default rules (lowest priority)
Recommended file structure:
project/
├── src/
│ ├── main.cpp # Your code with in-place annotations
│ └── utils.cpp # More code with in-place annotations
├── include/
│ ├── stl_lifetimes.hpp # STL annotations (from RustyCpp)
│ ├── external_annotations.hpp # External function annotations
│ └── project_annotations.hpp # Your project-specific annotations
└── third_party/
└── library/ # Unannotated third-party code
| Caller → Can Call | @safe | @unsafe | Undeclared |
|---|---|---|---|
| @safe | ✅ Yes | ✅ Yes | ❌ No |
| @unsafe | ✅ Yes | ✅ Yes | ✅ Yes |
| Undeclared | ✅ Yes | ✅ Yes | ✅ Yes |
- @unsafe ≠ Undeclared: Unsafe is explicitly marked (audited), undeclared is not
- Safe enforces auditing: Can't call unaudited code
- Undeclared can call undeclared: Legacy code continues to work without modification
- STL is undeclared by default: Must explicitly mark before use in safe code
- Audit ratchet: Once marked @safe, all dependencies must be explicitly audited
// Scenario 1: Legacy code chains work fine
void helper() { } // undeclared
void process() { helper(); } // undeclared calling undeclared ✅
void init() { process(); } // undeclared calling undeclared ✅
// Scenario 2: Safe function forces auditing
// @safe
void secure_function() {
// helper(); // ERROR: safe cannot call undeclared ❌
// Must explicitly mark helper as @safe or @unsafe first
}
// Scenario 3: Unsafe as escape hatch
// @unsafe
void low_level_function() {
helper(); // OK: unsafe can call undeclared ✅
process(); // OK: unsafe can call undeclared ✅
init(); // OK: unsafe can call undeclared ✅
}- Start by marking obviously safe functions as
@safe - Mark dangerous functions as
@unsafe - Leave legacy code undeclared initially
- Gradually audit and mark undeclared functions
- Use external annotations for dependencies
- In-place: Directly above declaration
- External: In dedicated header files
- STL: Include provided headers
// Good: Clear what's happening
// @lifetime: (&'container, size_t) -> &'container
// Less clear:
// @lifetime: (&'a, size_t) -> &'a// @lifetime: (&'a, &'b) -> &'a where 'a: 'b
// Returns first parameter, which must outlive second
const T& keepFirst(const T& long_lived, const T& short_lived);// Problem:
const T& ref = container.get();
container.modify(); // ERROR
// Solution: Limit scope of borrows
{
const T& ref = container.get();
// use ref
} // ref out of scope
container.modify(); // OK now// Problem:
auto ptr2 = std::move(ptr1);
ptr1->method(); // ERROR
// Solution: Don't use after move
auto ptr2 = std::move(ptr1);
ptr2->method(); // Use ptr2 instead// Problem:
// @lifetime: (&'a, &'b) -> &'a
// Missing: where 'a: 'b
// Solution: Add constraint
// @lifetime: (&'a, &'b) -> &'a where 'a: 'b# Check file with annotations
rusty-cpp-checker annotated_file.cpp
# With external annotations
rusty-cpp-checker -I include file.cpp# CMake
target_compile_definitions(project PRIVATE
RUSTYCPP_ANNOTATIONS_ENABLED=1
)# See how annotations are parsed
rusty-cpp-checker -vv file.cpp
# JSON output for tooling
rusty-cpp-checker --format json file.cpp- Not recognized: Check exact syntax
- Too restrictive: Consider
ownedinstead of borrowed - Missing: Check file is included
- Conflicting: In-place overrides external
Planned improvements:
- Lifetime elision (automatic inference)
- Method lifetime annotations
- Generic lifetime parameters
- Async lifetime tracking
- IDE quick fixes