What aspect-rs Can Do
This section provides a concrete overview of aspect-rs capabilities, limitations, and use cases.
Supported Features
✅ Four Advice Types
aspect-rs supports all common advice types:
| Advice | When it runs | Use cases |
|---|---|---|
before | Before function | Logging, validation, authorization |
after | After success | Logging, metrics, cleanup |
after_throwing | On error/panic | Error logging, alerting, rollback |
around | Wraps execution | Timing, caching, transactions, retry |
✅ Function Types Supported
aspect-rs works with various function types:
#![allow(unused)]
fn main() {
// Regular functions
#[aspect(LoggingAspect::new())]
fn sync_function(x: i32) -> i32 { x * 2 }
// Async functions
#[aspect(LoggingAspect::new())]
async fn async_function(x: i32) -> i32 { x * 2 }
// Generic functions
#[aspect(LoggingAspect::new())]
fn generic_function<T: Display>(x: T) -> String {
x.to_string()
}
// Functions with lifetimes
#[aspect(LoggingAspect::new())]
fn with_lifetime<'a>(s: &'a str) -> &'a str { s }
// Methods (associated functions)
impl MyStruct {
#[aspect(LoggingAspect::new())]
fn method(&self) -> i32 { self.value }
}
// Functions returning Result
#[aspect(LoggingAspect::new())]
fn returns_result(x: i32) -> Result<i32, Error> {
Ok(x * 2)
}
}
✅ Multiple Aspects Composition
Stack multiple aspects on a single function:
#![allow(unused)]
fn main() {
#[aspect(LoggingAspect::new())]
#[aspect(TimingAspect::new())]
#[aspect(CachingAspect::new())]
#[aspect(MetricsAspect::new())]
fn fetch_user(id: u64) -> Result<User, Error> {
database::query_user(id)
}
}
Execution order (outermost first):
- MetricsAspect::before()
- CachingAspect::around() → checks cache
- TimingAspect::around() → starts timer
- LoggingAspect::before()
- Function executes
- LoggingAspect::after()
- TimingAspect::around() → records time
- CachingAspect::around() → caches result
- MetricsAspect::after()
✅ Thread Safety
All aspects must implement Send + Sync:
#![allow(unused)]
fn main() {
pub trait Aspect: Send + Sync {
// ...
}
}
This ensures aspects can be used safely across threads.
✅ Eight Standard Aspects
The aspect-std crate provides production-ready aspects:
#![allow(unused)]
fn main() {
use aspect_std::*;
// 1. Logging
#[aspect(LoggingAspect::new())]
fn process_order(order: Order) { ... }
// 2. Timing/Performance Monitoring
#[aspect(TimingAspect::new())]
fn expensive_calculation(n: u64) -> u64 { ... }
// 3. Caching/Memoization
#[aspect(CachingAspect::new())]
fn fibonacci(n: u64) -> u64 { ... }
// 4. Metrics Collection
#[aspect(MetricsAspect::new())]
fn api_endpoint() -> Response { ... }
// 5. Rate Limiting
#[aspect(RateLimitAspect::new(100, Duration::from_secs(60)))]
fn api_call() -> Response { ... }
// 6. Circuit Breaker
#[aspect(CircuitBreakerAspect::new(5, Duration::from_secs(30)))]
fn external_service() -> Result<Data, Error> { ... }
// 7. Authorization (RBAC)
#[aspect(AuthorizationAspect::require_role("admin", get_user_roles))]
fn delete_user(id: u64) -> Result<(), Error> { ... }
// 8. Validation
#[aspect(ValidationAspect::new())]
fn create_user(email: String) -> Result<User, Error> { ... }
}
✅ Zero Runtime Dependencies
The aspect-core crate has zero dependencies:
[dependencies]
# No runtime dependencies!
Generated code doesn’t depend on aspect-rs at runtime. The aspect logic is inlined at compile time.
✅ Low Overhead
Benchmarks show <10ns overhead for simple aspects:
| Aspect Type | Baseline | With Aspect | Overhead |
|---|---|---|---|
| Empty function | 10ns | 12ns | +2ns (20%) |
| Logging | 15ns | 17ns | +2ns (13%) |
| Timing | 20ns | 22ns | +2ns (10%) |
| Caching (hit) | 5ns | 7ns | +2ns (40%) |
| Caching (miss) | 100ns | 102ns | +2ns (2%) |
The overhead is a constant ~2ns, not proportional to function complexity.
✅ Compile-Time Type Checking
All aspect code is type-checked:
#![allow(unused)]
fn main() {
#[aspect(LoggingAspect::new())]
fn returns_number() -> i32 { 42 }
impl Aspect for LoggingAspect {
fn after(&self, ctx: &JoinPoint, result: &dyn Any) {
// This would fail at compile time if types don't match
if let Some(num) = result.downcast_ref::<i32>() {
println!("Result: {}", num);
}
}
}
}
✅ Ownership and Lifetime Safety
Aspects respect Rust’s ownership rules:
#![allow(unused)]
fn main() {
#[aspect(LoggingAspect::new())]
fn takes_ownership(data: Vec<String>) -> Vec<String> {
// 'data' is moved, not borrowed
data.into_iter().map(|s| s.to_uppercase()).collect()
}
#[aspect(LoggingAspect::new())]
fn borrows_data(data: &[String]) -> usize {
// 'data' is borrowed, not moved
data.len()
}
}
The macro preserves the original function’s ownership semantics.
Current Limitations (Phase 1-2)
⚠️ Per-Function Annotation Required
Currently, you must annotate each function individually:
#![allow(unused)]
fn main() {
// Must annotate every function
#[aspect(LoggingAspect::new())]
fn function1() { }
#[aspect(LoggingAspect::new())]
fn function2() { }
#[aspect(LoggingAspect::new())]
fn function3() { }
}
Phase 3 will support pattern-based matching:
#![allow(unused)]
fn main() {
// Phase 3 - Annotate once, apply to all matching functions
#[advice(
pointcut = "execution(pub fn *(..)) && within(crate::api)",
advice = "before"
)]
static LOGGER: LoggingAspect = LoggingAspect::new();
}
⚠️ Function Execution Only
Currently, only function calls are join points. You cannot intercept:
- Field access (
obj.field) - Method calls at call-site (
obj.method()vsmethoddefinition) - Static initialization
- Exception handling
Phase 3+ will add these capabilities.
⚠️ Limited Pointcut Expressions
Phase 1-2 only supports direct aspect application. No pattern matching like:
#![allow(unused)]
fn main() {
// Not yet supported in Phase 1-2
pointcut = "execution(* create_*(..))" // All functions starting with "create_"
pointcut = "@annotation(#[cached])" // All functions with #[cached]
pointcut = "within(crate::api::*)" // All functions in api module
}
Phase 3 will add full pointcut expression support.
⚠️ No Inter-Type Declarations
AspectJ allows adding fields/methods to existing types. aspect-rs does not support this.
// AspectJ - Can add fields to existing classes
aspect LoggingAspect {
private int UserService.callCount; // Add field
public void UserService.logCalls() { // Add method
System.out.println(this.callCount);
}
}
Not planned for aspect-rs (violates Rust’s encapsulation).
Use Cases
✅ Ideal Use Cases
aspect-rs excels at:
-
Logging & Observability
- Entry/exit logging
- Distributed tracing correlation IDs
- Structured logging with context
-
Performance Monitoring
- Execution time measurement
- Slow function warnings
- Performance regression detection
-
Caching
- Memoization of expensive computations
- Cache invalidation strategies
- Cache hit/miss metrics
-
Security & Authorization
- Role-based access control (RBAC)
- Authentication checks
- Audit logging
-
Resilience Patterns
- Circuit breakers
- Retry logic
- Timeouts
- Rate limiting
-
Metrics & Analytics
- Call counters
- Latency percentiles
- Error rates
- Business metrics
-
Transaction Management
- Database transaction boundaries
- Rollback on error
- Nested transactions
-
Validation
- Input validation
- Precondition checks
- Invariant verification
⚠️ Not Ideal Use Cases
aspect-rs is not the best choice for:
- One-off functionality - Just write manual code
- HTTP-specific middleware - Use framework middleware (Actix, Tower)
- Runtime-swappable behavior - Use trait objects or strategy pattern
- Bytecode manipulation - Not possible in Rust
- Extreme zero-dependency requirements - Even though aspect-core has zero deps, you still need the macro at compile time
Feature Comparison
| Feature | Phase 1 (MVP) | Phase 2 (Production) | Phase 3 (Automatic) |
|---|---|---|---|
| Basic macro weaving | ✅ | ✅ | ✅ |
| Four advice types | ✅ | ✅ | ✅ |
| Async support | ✅ | ✅ | ✅ |
| Generic functions | ✅ | ✅ | ✅ |
| Standard aspects | ⚠️ Basic | ✅ 8 aspects | ✅ 8+ aspects |
| Per-function annotation | ✅ Required | ✅ Required | ⚠️ Optional |
| Pointcut expressions | ❌ | ⚠️ Limited | ✅ Full |
| Pattern matching | ❌ | ❌ | ✅ |
| Call-site interception | ❌ | ❌ | ✅ Planned |
| Field access | ❌ | ❌ | ✅ Planned |
Summary
What aspect-rs does well:
- ✅ Zero-cost abstraction (<10ns overhead)
- ✅ Compile-time type safety
- ✅ Production-ready standard aspects
- ✅ Async and generic function support
- ✅ Clean separation of concerns
Current limitations (to be addressed in Phase 3):
- ⚠️ Per-function annotations required
- ⚠️ Function execution only (no field access yet)
- ⚠️ Limited pointcut expressions
Not in scope:
- ❌ Runtime aspect swapping
- ❌ Bytecode manipulation
- ❌ Inter-type declarations
Next Steps
Ready to try aspect-rs? Continue to Chapter 3: Getting Started for a 5-minute quickstart!
Want to understand the implementation? Jump to Chapter 6: Architecture.