ownership-borrowing — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited ownership-borrowing (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Understand and apply Rust's ownership system to write memory-safe code without a garbage collector. Master borrowing rules, lifetimes, and smart pointers.
Three rules govern all Rust memory:
fn main() {
let name = String::from("Alice"); // name owns the String
let greeting = greet(name); // ownership moves to greet
// println!("{name}"); // ERROR: name was moved
println!("{greeting}");
}
fn greet(name: String) -> String { // takes ownership
format!("Hello, {name}!") // returns a new owned String
}Borrow data without taking ownership. Two rules:
&T) OR exactly one mutable reference (&mut T).fn analyze(data: &[i32]) -> (i32, i32) {
// Shared borrow: can read, cannot modify
let sum: i32 = data.iter().sum();
let count = data.len() as i32;
(sum, count)
}
fn normalize(data: &mut Vec<f64>) {
// Mutable borrow: can read and modify
let max = data.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
if max != 0.0 {
for val in data.iter_mut() {
*val /= max;
}
}
}
fn main() {
let mut values = vec![1.0, 2.0, 3.0];
normalize(&mut values); // mutable borrow
let (sum, _) = analyze(&[1, 2, 3]); // shared borrow
println!("{sum}");
}Lifetimes tell the compiler how long references are valid. Most are inferred; annotate when the compiler cannot determine the relationship.
// The returned reference lives as long as the shortest input lifetime
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
// Lifetime in structs: the struct cannot outlive the referenced data
struct Excerpt<'a> {
text: &'a str,
}
impl<'a> Excerpt<'a> {
fn first_word(&self) -> &'a str {
self.text.split_whitespace().next().unwrap_or("")
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let excerpt = Excerpt {
text: novel.split('.').next().unwrap(),
};
println!("{}", excerpt.first_word());
}Use smart pointers when ownership rules need more flexibility.
// Box<T>: heap allocation, single owner
fn build_tree() -> Box<Node> {
Box::new(Node {
value: 1,
left: Some(Box::new(Node { value: 2, left: None, right: None })),
right: Some(Box::new(Node { value: 3, left: None, right: None })),
})
}
// Rc<T>: shared ownership, single-threaded (reference counted)
use std::rc::Rc;
fn shared_config() {
let config = Rc::new(AppConfig::default());
let service_a = Service::new(Rc::clone(&config));
let service_b = Service::new(Rc::clone(&config));
// Both services share the same config; dropped when last Rc is dropped
}
// Arc<T>: shared ownership, thread-safe (atomic reference counted)
use std::sync::Arc;
fn concurrent_cache() {
let cache = Arc::new(Mutex::new(HashMap::new()));
let handles: Vec<_> = (0..4)
.map(|i| {
let cache = Arc::clone(&cache);
std::thread::spawn(move || {
cache.lock().unwrap().insert(i, i * 10);
})
})
.collect();
for h in handles {
h.join().unwrap();
}
}Copy: bitwise copy, implicit, for small stack types (integers, bools, tuples of Copy types). Clone: explicit .clone(), for types that need deep duplication.
// Copy: automatically duplicated on assignment
let x: i32 = 42;
let y = x; // x is copied, both x and y are valid
println!("{x} {y}");
// Clone: explicit duplication
let a = String::from("hello");
let b = a.clone(); // deep copy -- both a and b are valid
println!("{a} {b}");
// Without clone, a would be moved:
let c = String::from("world");
let d = c; // c is MOVED to d
// println!("{c}"); // ERROR: c was moved
// Derive both when your struct contains only Copy types
#[derive(Debug, Clone, Copy)]
struct Point {
x: f64,
y: f64,
}Solutions to frequent borrow checker issues.
// Problem: cannot borrow as mutable because also borrowed as immutable
// Solution: limit the scope of the immutable borrow
fn update_map(map: &mut HashMap<String, Vec<i32>>, key: &str) {
// Use entry API to avoid double-borrow
map.entry(key.to_string())
.or_insert_with(Vec::new)
.push(42);
}
// Problem: returning a reference to a local variable
// Solution: return owned data
fn create_greeting(name: &str) -> String {
// Return owned String, not &str
format!("Hello, {name}!")
}
// Problem: self-referential struct
// Solution: use indices instead of references, or use Pin
struct Document {
content: String,
// Instead of &str references into content, store byte offsets
highlights: Vec<(usize, usize)>,
}
impl Document {
fn highlighted_text(&self, idx: usize) -> &str {
let (start, end) = self.highlights[idx];
&self.content[start..end]
}
}.clone() everywhere is a sign ofmisunderstood ownership. Restructure code to use borrows instead.
Single-threaded code should use Rc or plain ownership.
annotate when the compiler asks or when the relationship is ambiguous.
must return owned types. References can only point to data that outlives them.
| Type | Ownership | Thread-safe | Heap | Use Case |
|---|---|---|---|---|
T | Sole owner | N/A | Stack* | Default |
&T | Shared borrow | Yes (if T: Sync) | No | Read access |
&mut T | Exclusive borrow | No | No | Write access |
Box<T> | Sole owner | N/A | Yes | Large/recursive types |
Rc<T> | Shared | No | Yes | Single-thread sharing |
Arc<T> | Shared | Yes | Yes | Multi-thread sharing |
Move vs Copy: types implementing Copy are duplicated on assignment; all others are moved.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.