agentic-team-templates 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.js +4 -0
- package/src/index.test.js +1 -0
- package/templates/rust-expert/.cursorrules/concurrency.md +250 -0
- package/templates/rust-expert/.cursorrules/ecosystem-and-tooling.md +299 -0
- package/templates/rust-expert/.cursorrules/error-handling.md +190 -0
- package/templates/rust-expert/.cursorrules/overview.md +142 -0
- package/templates/rust-expert/.cursorrules/ownership-and-borrowing.md +204 -0
- package/templates/rust-expert/.cursorrules/performance-and-unsafe.md +256 -0
- package/templates/rust-expert/.cursorrules/testing.md +300 -0
- package/templates/rust-expert/.cursorrules/traits-and-generics.md +236 -0
- package/templates/rust-expert/CLAUDE.md +283 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-team-templates",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "AI coding assistant templates for Cursor IDE. Pre-configured rules and guidelines that help AI assistants write better code. - use at your own risk",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cursor",
|
package/src/index.js
CHANGED
|
@@ -56,6 +56,10 @@ const TEMPLATES = {
|
|
|
56
56
|
description: 'Mobile applications (React Native, Flutter, native iOS/Android)',
|
|
57
57
|
rules: ['navigation.md', 'offline-first.md', 'overview.md', 'performance.md', 'testing.md']
|
|
58
58
|
},
|
|
59
|
+
'rust-expert': {
|
|
60
|
+
description: 'Principal-level Rust engineering (ownership, concurrency, unsafe, traits, async)',
|
|
61
|
+
rules: ['concurrency.md', 'ecosystem-and-tooling.md', 'error-handling.md', 'overview.md', 'ownership-and-borrowing.md', 'performance-and-unsafe.md', 'testing.md', 'traits-and-generics.md']
|
|
62
|
+
},
|
|
59
63
|
'platform-engineering': {
|
|
60
64
|
description: 'Internal developer platforms, infrastructure automation, and reliability engineering',
|
|
61
65
|
rules: ['ci-cd.md', 'developer-experience.md', 'infrastructure-as-code.md', 'kubernetes.md', 'observability.md', 'overview.md', 'security.md', 'testing.md']
|
package/src/index.test.js
CHANGED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# Rust Concurrency
|
|
2
|
+
|
|
3
|
+
Rust prevents data races at compile time through ownership and the `Send`/`Sync` marker traits. Fearless concurrency is real — but it doesn't mean you can ignore deadlocks, race conditions at the logic level, or performance pitfalls.
|
|
4
|
+
|
|
5
|
+
## Send and Sync
|
|
6
|
+
|
|
7
|
+
```rust
|
|
8
|
+
// Send: safe to transfer ownership to another thread
|
|
9
|
+
// Sync: safe to share references (&T) between threads
|
|
10
|
+
// These are auto-traits — the compiler derives them automatically
|
|
11
|
+
|
|
12
|
+
// Most types are Send + Sync
|
|
13
|
+
// Rc<T> is NOT Send or Sync — use Arc<T> for multi-threaded code
|
|
14
|
+
// RefCell<T> is Send but NOT Sync — use Mutex<T> for multi-threaded code
|
|
15
|
+
// Raw pointers are neither — wrap them in types that uphold invariants
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Threading
|
|
19
|
+
|
|
20
|
+
### std::thread
|
|
21
|
+
|
|
22
|
+
```rust
|
|
23
|
+
use std::thread;
|
|
24
|
+
|
|
25
|
+
// Scoped threads (Go 1.63+) — borrows from the parent stack are allowed
|
|
26
|
+
let mut data = vec![1, 2, 3, 4];
|
|
27
|
+
thread::scope(|s| {
|
|
28
|
+
let (left, right) = data.split_at_mut(2);
|
|
29
|
+
|
|
30
|
+
s.spawn(|| {
|
|
31
|
+
left.iter_mut().for_each(|x| *x *= 2);
|
|
32
|
+
});
|
|
33
|
+
s.spawn(|| {
|
|
34
|
+
right.iter_mut().for_each(|x| *x *= 3);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
// Both threads are guaranteed to finish before scope exits
|
|
38
|
+
// No Arc, no clone, no join handles to manage
|
|
39
|
+
assert_eq!(data, [2, 4, 9, 12]);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Shared State with Arc + Mutex
|
|
43
|
+
|
|
44
|
+
```rust
|
|
45
|
+
use std::sync::{Arc, Mutex};
|
|
46
|
+
|
|
47
|
+
let counter = Arc::new(Mutex::new(0));
|
|
48
|
+
let mut handles = vec![];
|
|
49
|
+
|
|
50
|
+
for _ in 0..10 {
|
|
51
|
+
let counter = Arc::clone(&counter);
|
|
52
|
+
handles.push(thread::spawn(move || {
|
|
53
|
+
let mut num = counter.lock().unwrap();
|
|
54
|
+
*num += 1;
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for handle in handles {
|
|
59
|
+
handle.join().unwrap();
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### RwLock for Read-Heavy Workloads
|
|
64
|
+
|
|
65
|
+
```rust
|
|
66
|
+
use std::sync::RwLock;
|
|
67
|
+
|
|
68
|
+
let config = Arc::new(RwLock::new(Config::default()));
|
|
69
|
+
|
|
70
|
+
// Multiple readers — no blocking
|
|
71
|
+
let cfg = config.read().unwrap();
|
|
72
|
+
println!("{}", cfg.setting);
|
|
73
|
+
|
|
74
|
+
// Single writer — exclusive access
|
|
75
|
+
let mut cfg = config.write().unwrap();
|
|
76
|
+
cfg.setting = new_value;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Channels
|
|
80
|
+
|
|
81
|
+
```rust
|
|
82
|
+
use std::sync::mpsc;
|
|
83
|
+
|
|
84
|
+
// Multiple producers, single consumer
|
|
85
|
+
let (tx, rx) = mpsc::channel();
|
|
86
|
+
|
|
87
|
+
let tx2 = tx.clone(); // Clone sender for second producer
|
|
88
|
+
|
|
89
|
+
thread::spawn(move || {
|
|
90
|
+
tx.send(Message::Data(vec![1, 2, 3])).unwrap();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
thread::spawn(move || {
|
|
94
|
+
tx2.send(Message::Data(vec![4, 5, 6])).unwrap();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Receive all messages
|
|
98
|
+
for msg in rx {
|
|
99
|
+
process(msg);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// crossbeam-channel for multi-producer multi-consumer, select!, bounded channels
|
|
103
|
+
use crossbeam_channel::{bounded, select};
|
|
104
|
+
|
|
105
|
+
let (tx, rx) = bounded(100); // Backpressure at 100 items
|
|
106
|
+
|
|
107
|
+
select! {
|
|
108
|
+
recv(rx) -> msg => handle(msg.unwrap()),
|
|
109
|
+
default(Duration::from_secs(1)) => println!("timeout"),
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Async/Await
|
|
114
|
+
|
|
115
|
+
### Tokio Runtime
|
|
116
|
+
|
|
117
|
+
```rust
|
|
118
|
+
#[tokio::main]
|
|
119
|
+
async fn main() -> Result<()> {
|
|
120
|
+
let listener = TcpListener::bind("0.0.0.0:8080").await?;
|
|
121
|
+
|
|
122
|
+
loop {
|
|
123
|
+
let (stream, addr) = listener.accept().await?;
|
|
124
|
+
tokio::spawn(async move {
|
|
125
|
+
if let Err(e) = handle_connection(stream).await {
|
|
126
|
+
eprintln!("connection error from {addr}: {e}");
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Async Patterns
|
|
134
|
+
|
|
135
|
+
```rust
|
|
136
|
+
// Concurrent execution with join
|
|
137
|
+
let (users, posts) = tokio::join!(
|
|
138
|
+
fetch_users(&client),
|
|
139
|
+
fetch_posts(&client),
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// Race — first to complete wins
|
|
143
|
+
tokio::select! {
|
|
144
|
+
result = fetch_data() => handle_data(result),
|
|
145
|
+
_ = tokio::time::sleep(Duration::from_secs(5)) => {
|
|
146
|
+
return Err(anyhow!("fetch timed out"));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Bounded concurrency with semaphore
|
|
151
|
+
use tokio::sync::Semaphore;
|
|
152
|
+
|
|
153
|
+
let semaphore = Arc::new(Semaphore::new(10));
|
|
154
|
+
let mut handles = vec![];
|
|
155
|
+
|
|
156
|
+
for url in urls {
|
|
157
|
+
let permit = semaphore.clone().acquire_owned().await?;
|
|
158
|
+
handles.push(tokio::spawn(async move {
|
|
159
|
+
let result = fetch(url).await;
|
|
160
|
+
drop(permit); // Release when done
|
|
161
|
+
result
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Stream processing
|
|
166
|
+
use tokio_stream::StreamExt;
|
|
167
|
+
|
|
168
|
+
let mut stream = tokio_stream::iter(items)
|
|
169
|
+
.map(|item| async move { process(item).await })
|
|
170
|
+
.buffer_unordered(10); // Process up to 10 concurrently
|
|
171
|
+
|
|
172
|
+
while let Some(result) = stream.next().await {
|
|
173
|
+
handle(result?);
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Async Traits
|
|
178
|
+
|
|
179
|
+
```rust
|
|
180
|
+
// Since Rust 1.75: async fn in traits works natively
|
|
181
|
+
pub trait Repository {
|
|
182
|
+
async fn find_by_id(&self, id: &str) -> Result<Option<Entity>>;
|
|
183
|
+
async fn save(&self, entity: &Entity) -> Result<()>;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// For trait objects (dyn), use the async-trait crate or
|
|
187
|
+
// return Pin<Box<dyn Future>> manually
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Cancellation Safety
|
|
191
|
+
|
|
192
|
+
```rust
|
|
193
|
+
// tokio::select! can cancel futures — understand what that means
|
|
194
|
+
// A future dropped mid-execution won't run its remaining code
|
|
195
|
+
|
|
196
|
+
// Cancellation-safe: reading from a channel (no partial state)
|
|
197
|
+
// NOT cancellation-safe: reading into a buffer (partial read lost)
|
|
198
|
+
|
|
199
|
+
tokio::select! {
|
|
200
|
+
// Safe: mpsc::Receiver::recv is cancellation-safe
|
|
201
|
+
msg = rx.recv() => { ... }
|
|
202
|
+
|
|
203
|
+
// DANGEROUS if buf has partial state from a previous iteration
|
|
204
|
+
n = reader.read(&mut buf) => { ... }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// When in doubt, consult tokio's docs for each method's cancellation safety
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Atomics
|
|
211
|
+
|
|
212
|
+
```rust
|
|
213
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
214
|
+
|
|
215
|
+
static REQUEST_COUNT: AtomicU64 = AtomicU64::new(0);
|
|
216
|
+
|
|
217
|
+
fn handle_request() {
|
|
218
|
+
REQUEST_COUNT.fetch_add(1, Ordering::Relaxed);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Ordering guide:
|
|
222
|
+
// Relaxed — no ordering guarantees, just atomicity (counters)
|
|
223
|
+
// Acquire — subsequent reads see writes before the Release
|
|
224
|
+
// Release — previous writes are visible to Acquire loads
|
|
225
|
+
// SeqCst — total order, strongest guarantee, rarely needed
|
|
226
|
+
// If unsure, use SeqCst and optimize later with profiling data
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Anti-Patterns
|
|
230
|
+
|
|
231
|
+
```rust
|
|
232
|
+
// Never: Holding a MutexGuard across an await point
|
|
233
|
+
let guard = mutex.lock().await;
|
|
234
|
+
do_async_work().await; // Deadlock risk — guard is held across await
|
|
235
|
+
drop(guard);
|
|
236
|
+
// Instead: lock, extract data, drop guard, then await
|
|
237
|
+
|
|
238
|
+
// Never: Spawning tasks without cancellation or shutdown strategy
|
|
239
|
+
tokio::spawn(async { loop { do_work().await; } }); // How does this stop?
|
|
240
|
+
|
|
241
|
+
// Never: Blocking in async context
|
|
242
|
+
std::thread::sleep(Duration::from_secs(1)); // Blocks the executor thread!
|
|
243
|
+
tokio::time::sleep(Duration::from_secs(1)).await; // Use this instead
|
|
244
|
+
|
|
245
|
+
// For CPU-bound work in async context:
|
|
246
|
+
tokio::task::spawn_blocking(|| expensive_computation()).await?;
|
|
247
|
+
|
|
248
|
+
// Never: Using std::sync::Mutex in async code (blocks the executor)
|
|
249
|
+
// Use tokio::sync::Mutex instead when you must hold across awaits
|
|
250
|
+
```
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# Rust Ecosystem and Tooling
|
|
2
|
+
|
|
3
|
+
The Rust ecosystem is mature and opinionated. The toolchain is best-in-class. Know the tools and the community-standard crates.
|
|
4
|
+
|
|
5
|
+
## Cargo
|
|
6
|
+
|
|
7
|
+
### Cargo.toml Best Practices
|
|
8
|
+
|
|
9
|
+
```toml
|
|
10
|
+
[package]
|
|
11
|
+
name = "my-crate"
|
|
12
|
+
version = "0.1.0"
|
|
13
|
+
edition = "2021"
|
|
14
|
+
rust-version = "1.75" # Minimum supported Rust version (MSRV)
|
|
15
|
+
description = "A brief description"
|
|
16
|
+
license = "MIT OR Apache-2.0"
|
|
17
|
+
repository = "https://github.com/user/repo"
|
|
18
|
+
|
|
19
|
+
[dependencies]
|
|
20
|
+
serde = { version = "1", features = ["derive"] }
|
|
21
|
+
tokio = { version = "1", features = ["full"] }
|
|
22
|
+
|
|
23
|
+
[dev-dependencies]
|
|
24
|
+
criterion = { version = "0.5", features = ["html_reports"] }
|
|
25
|
+
proptest = "1"
|
|
26
|
+
tempfile = "3"
|
|
27
|
+
|
|
28
|
+
[profile.release]
|
|
29
|
+
lto = true # Link-time optimization
|
|
30
|
+
codegen-units = 1 # Slower compile, better optimization
|
|
31
|
+
strip = true # Strip debug symbols from binary
|
|
32
|
+
|
|
33
|
+
[profile.dev]
|
|
34
|
+
opt-level = 0 # Fast compile for development
|
|
35
|
+
|
|
36
|
+
# Feature flags must be additive
|
|
37
|
+
[features]
|
|
38
|
+
default = []
|
|
39
|
+
metrics = ["prometheus"]
|
|
40
|
+
tracing = ["tracing-subscriber", "tracing-opentelemetry"]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Workspace Management
|
|
44
|
+
|
|
45
|
+
```toml
|
|
46
|
+
# Root Cargo.toml
|
|
47
|
+
[workspace]
|
|
48
|
+
members = ["crates/*"]
|
|
49
|
+
resolver = "2"
|
|
50
|
+
|
|
51
|
+
# Share dependencies across workspace
|
|
52
|
+
[workspace.dependencies]
|
|
53
|
+
serde = { version = "1", features = ["derive"] }
|
|
54
|
+
tokio = { version = "1", features = ["full"] }
|
|
55
|
+
anyhow = "1"
|
|
56
|
+
|
|
57
|
+
# In member Cargo.toml:
|
|
58
|
+
[dependencies]
|
|
59
|
+
serde = { workspace = true }
|
|
60
|
+
tokio = { workspace = true }
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Essential Cargo Commands
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
cargo build --release # Optimized build
|
|
67
|
+
cargo test -- --nocapture # Show println! output in tests
|
|
68
|
+
cargo doc --open # Build and open docs
|
|
69
|
+
cargo tree # Dependency tree
|
|
70
|
+
cargo tree -d # Show duplicate dependencies
|
|
71
|
+
cargo update # Update Cargo.lock to latest compatible
|
|
72
|
+
cargo audit # Check for known vulnerabilities
|
|
73
|
+
cargo deny check # License and advisory checks
|
|
74
|
+
cargo expand # Show macro expansion
|
|
75
|
+
cargo outdated # Show outdated dependencies
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Linting and Formatting
|
|
79
|
+
|
|
80
|
+
### Clippy Configuration
|
|
81
|
+
|
|
82
|
+
```toml
|
|
83
|
+
# clippy.toml or .clippy.toml
|
|
84
|
+
cognitive-complexity-threshold = 25
|
|
85
|
+
too-many-arguments-threshold = 7
|
|
86
|
+
type-complexity-threshold = 250
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```rust
|
|
90
|
+
// In lib.rs or main.rs — project-wide lint configuration
|
|
91
|
+
#![warn(clippy::all, clippy::pedantic)]
|
|
92
|
+
#![allow(clippy::module_name_repetitions)] // Allow if your API style needs it
|
|
93
|
+
#![deny(clippy::unwrap_used)] // No unwrap in library code
|
|
94
|
+
#![deny(unsafe_code)] // Deny unsafe unless explicitly needed
|
|
95
|
+
|
|
96
|
+
// Per-module overrides when justified
|
|
97
|
+
#[allow(clippy::cast_possible_truncation)]
|
|
98
|
+
// Justification: value is guaranteed < 256 by the protocol spec
|
|
99
|
+
fn protocol_byte(value: u32) -> u8 {
|
|
100
|
+
value as u8
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### rustfmt
|
|
105
|
+
|
|
106
|
+
```toml
|
|
107
|
+
# rustfmt.toml
|
|
108
|
+
edition = "2021"
|
|
109
|
+
max_width = 100
|
|
110
|
+
use_field_init_shorthand = true
|
|
111
|
+
use_try_shorthand = true
|
|
112
|
+
imports_granularity = "Crate"
|
|
113
|
+
group_imports = "StdExternalCrate"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Essential Crates
|
|
117
|
+
|
|
118
|
+
### Serialization
|
|
119
|
+
- **serde** + **serde_json** — the serialization framework, period
|
|
120
|
+
- **toml** — config files
|
|
121
|
+
- **bincode** — compact binary format
|
|
122
|
+
|
|
123
|
+
### Async Runtime
|
|
124
|
+
- **tokio** — the dominant async runtime (multi-threaded, work-stealing)
|
|
125
|
+
- **async-std** — alternative, closer to std API
|
|
126
|
+
|
|
127
|
+
### Web
|
|
128
|
+
- **axum** — ergonomic web framework built on Tower and Hyper
|
|
129
|
+
- **reqwest** — HTTP client
|
|
130
|
+
- **tonic** — gRPC
|
|
131
|
+
|
|
132
|
+
### Database
|
|
133
|
+
- **sqlx** — compile-time checked SQL queries
|
|
134
|
+
- **diesel** — ORM with type-safe query builder
|
|
135
|
+
- **sea-orm** — async ORM built on sqlx
|
|
136
|
+
|
|
137
|
+
### Error Handling
|
|
138
|
+
- **thiserror** — derive Error for library error types
|
|
139
|
+
- **anyhow** — flexible error handling for applications
|
|
140
|
+
|
|
141
|
+
### CLI
|
|
142
|
+
- **clap** — argument parsing (derive or builder API)
|
|
143
|
+
|
|
144
|
+
### Observability
|
|
145
|
+
- **tracing** — structured, async-aware instrumentation
|
|
146
|
+
- **metrics** — application metrics
|
|
147
|
+
- **opentelemetry** — distributed tracing
|
|
148
|
+
|
|
149
|
+
### Testing
|
|
150
|
+
- **proptest** — property-based testing
|
|
151
|
+
- **criterion** — statistical benchmarking
|
|
152
|
+
- **mockall** — mocking framework
|
|
153
|
+
- **wiremock** — HTTP mocking for integration tests
|
|
154
|
+
- **tempfile** — temporary files and directories for tests
|
|
155
|
+
- **insta** — snapshot testing
|
|
156
|
+
|
|
157
|
+
## CI/CD
|
|
158
|
+
|
|
159
|
+
### GitHub Actions
|
|
160
|
+
|
|
161
|
+
```yaml
|
|
162
|
+
name: CI
|
|
163
|
+
|
|
164
|
+
on: [push, pull_request]
|
|
165
|
+
|
|
166
|
+
env:
|
|
167
|
+
CARGO_TERM_COLOR: always
|
|
168
|
+
RUSTFLAGS: "-Dwarnings"
|
|
169
|
+
|
|
170
|
+
jobs:
|
|
171
|
+
check:
|
|
172
|
+
runs-on: ubuntu-latest
|
|
173
|
+
steps:
|
|
174
|
+
- uses: actions/checkout@v4
|
|
175
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
176
|
+
with:
|
|
177
|
+
components: rustfmt, clippy
|
|
178
|
+
- uses: Swatinem/rust-cache@v2
|
|
179
|
+
|
|
180
|
+
- run: cargo fmt -- --check
|
|
181
|
+
- run: cargo clippy --all-targets --all-features
|
|
182
|
+
- run: cargo test --all-features
|
|
183
|
+
- run: cargo doc --no-deps --all-features
|
|
184
|
+
|
|
185
|
+
# Miri for crates with unsafe code
|
|
186
|
+
miri:
|
|
187
|
+
runs-on: ubuntu-latest
|
|
188
|
+
steps:
|
|
189
|
+
- uses: actions/checkout@v4
|
|
190
|
+
- uses: dtolnay/rust-toolchain@nightly
|
|
191
|
+
with:
|
|
192
|
+
components: miri
|
|
193
|
+
- run: cargo +nightly miri test
|
|
194
|
+
|
|
195
|
+
# Security audit
|
|
196
|
+
audit:
|
|
197
|
+
runs-on: ubuntu-latest
|
|
198
|
+
steps:
|
|
199
|
+
- uses: actions/checkout@v4
|
|
200
|
+
- uses: rustsec/audit-check@v2
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Makefile
|
|
204
|
+
|
|
205
|
+
```makefile
|
|
206
|
+
.PHONY: check test lint fmt doc
|
|
207
|
+
|
|
208
|
+
check: fmt lint test
|
|
209
|
+
|
|
210
|
+
fmt:
|
|
211
|
+
cargo fmt -- --check
|
|
212
|
+
|
|
213
|
+
lint:
|
|
214
|
+
cargo clippy --all-targets --all-features -- -D warnings
|
|
215
|
+
|
|
216
|
+
test:
|
|
217
|
+
cargo test --all-features
|
|
218
|
+
|
|
219
|
+
doc:
|
|
220
|
+
cargo doc --no-deps --all-features --open
|
|
221
|
+
|
|
222
|
+
audit:
|
|
223
|
+
cargo audit
|
|
224
|
+
cargo deny check
|
|
225
|
+
|
|
226
|
+
bench:
|
|
227
|
+
cargo bench
|
|
228
|
+
|
|
229
|
+
coverage:
|
|
230
|
+
cargo tarpaulin --out Html --output-dir target/coverage
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Documentation
|
|
234
|
+
|
|
235
|
+
```rust
|
|
236
|
+
//! # My Crate
|
|
237
|
+
//!
|
|
238
|
+
//! `my_crate` provides utilities for processing data efficiently.
|
|
239
|
+
//!
|
|
240
|
+
//! ## Quick Start
|
|
241
|
+
//!
|
|
242
|
+
//! ```rust
|
|
243
|
+
//! use my_crate::process;
|
|
244
|
+
//!
|
|
245
|
+
//! let result = process("input data")?;
|
|
246
|
+
//! println!("{result}");
|
|
247
|
+
//! # Ok::<(), my_crate::Error>(())
|
|
248
|
+
//! ```
|
|
249
|
+
|
|
250
|
+
/// Processes the input data and returns a formatted result.
|
|
251
|
+
///
|
|
252
|
+
/// # Arguments
|
|
253
|
+
///
|
|
254
|
+
/// * `input` - A string slice containing the raw data
|
|
255
|
+
///
|
|
256
|
+
/// # Errors
|
|
257
|
+
///
|
|
258
|
+
/// Returns [`Error::InvalidInput`] if the input is malformed.
|
|
259
|
+
///
|
|
260
|
+
/// # Examples
|
|
261
|
+
///
|
|
262
|
+
/// ```
|
|
263
|
+
/// use my_crate::process;
|
|
264
|
+
///
|
|
265
|
+
/// let output = process("valid input").unwrap();
|
|
266
|
+
/// assert_eq!(output, "processed: valid input");
|
|
267
|
+
/// ```
|
|
268
|
+
pub fn process(input: &str) -> Result<String, Error> {
|
|
269
|
+
// ...
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Document panic conditions
|
|
273
|
+
/// # Panics
|
|
274
|
+
///
|
|
275
|
+
/// Panics if `divisor` is zero.
|
|
276
|
+
pub fn divide(a: i64, divisor: i64) -> i64 {
|
|
277
|
+
assert_ne!(divisor, 0, "divisor must be non-zero");
|
|
278
|
+
a / divisor
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Document safety requirements for unsafe functions
|
|
282
|
+
/// # Safety
|
|
283
|
+
///
|
|
284
|
+
/// - `ptr` must be valid for reads of `len` bytes
|
|
285
|
+
/// - `ptr` must be properly aligned for `T`
|
|
286
|
+
/// - The memory referenced must not be mutated during the lifetime of the returned slice
|
|
287
|
+
pub unsafe fn from_raw_parts<'a, T>(ptr: *const T, len: usize) -> &'a [T] {
|
|
288
|
+
std::slice::from_raw_parts(ptr, len)
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Dependency Hygiene
|
|
293
|
+
|
|
294
|
+
- **Audit regularly**: `cargo audit` for security advisories
|
|
295
|
+
- **Check licenses**: `cargo deny check licenses`
|
|
296
|
+
- **Minimize dependencies**: Every dependency is attack surface and compile time
|
|
297
|
+
- **Pin workspace deps**: Use `[workspace.dependencies]` for consistency
|
|
298
|
+
- **Review before adding**: Check maintenance status, download counts, and bus factor
|
|
299
|
+
- **Feature flags**: Only enable features you actually use
|