Loading page…
Rust walkthroughs
Loading page…
tracing::Span::enter differ from Span::in_scope for managing span lifetime?tracing::Span::enter returns a guard that keeps the span active for the duration of its lifetime (RAII pattern), while Span::in_scope takes a closure and executes it within the span's context, then exits immediately when the closure completes. The key difference is in how they manage scope: enter requires managing the guard explicitly and can lead to issues if the guard is held across .await points in async code, while in_scope has a clear lexical scope and prevents accidental misuse. For synchronous code, both work similarly, but in_scope is safer in mixed sync/async contexts because it cannot be held across await points.
use tracing::{span, Level};
fn main() {
let span = span!(Level::INFO, "my_span");
// enter returns a guard
let _enter = span.enter();
// All events here are within the span
tracing::info!("inside span");
// Guard dropped here, span exits
}enter() returns an Entered guard that exits the span when dropped.
use tracing::{span, Level};
fn main() {
let span = span!(Level::INFO, "my_span");
// in_scope takes a closure
span.in_scope(|| {
// All events here are within the span
tracing::info!("inside span");
});
// Span exited immediately after closure completes
}in_scope() executes a closure within the span's context.
use tracing::{span, Level};
fn main() {
let span = span!(Level::INFO, "operation");
// enter gives you control over when to exit
let guard = span.enter();
// Do some work
tracing::info!("processing");
// Can do other work while span is active
for i in 0..3 {
tracing::info!(iteration = i, "looping");
}
// Explicit control over exit
drop(guard);
tracing::info!("outside span");
}enter allows precise control over when the span exits via the guard.
use tracing::{span, Level};
fn main() {
let span = span!(Level::INFO, "operation");
// in_scope guarantees exit after closure
span.in_scope(|| {
tracing::info!("processing");
for i in 0..3 {
tracing::info!(iteration = i, "looping");
}
// Span exits automatically here
});
// No guard to remember to drop
tracing::info!("outside span");
}in_scope automatically exits after the closure, no guard to manage.
use tracing::{span, Level};
// WRONG: Using enter across await points
#[tokio::main]
async fn bad_async_pattern() {
let span = span!(Level::INFO, "async_op");
let _enter = span.enter(); // Guard created
// First await point
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
// PROBLEM: Span context lost after await!
// The guard is dropped at the await point
tracing::info!("this may not be in the span");
}
// CORRECT: Using in_scope for async work
#[tokio::main]
async fn good_async_pattern() {
let span = span!(Level::INFO, "async_op");
// in_scope cannot be held across await
span.in_scope(|| {
tracing::info!("sync work in span");
});
// For async work, use instrument
async_work().instrument(span).await;
}
async fn async_work() {
tracing::info!("async work");
}enter guards should not be held across .await points.
use tracing::{span, Level, Instrument};
#[tokio::main]
async fn main() {
let span = span!(Level::INFO, "async_operation");
// CORRECT: Use instrument for async code
async_function().instrument(span).await;
}
async fn async_function() {
// This runs within the span context
tracing::info!("async work started");
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
// Still in span context after await
tracing::info!("async work completed");
}For async code, use .instrument() instead of enter or in_scope.
use tracing::{span, Level};
#[tokio::main]
async fn main() {
let span = span!(Level::INFO, "operation");
// in_scope closure cannot be async
span.in_scope(|| {
tracing::info!("sync work only");
// Cannot put .await here - closure is sync
});
// For async work, use instrument
let result = do_async_work().await;
// Back to sync work with in_scope
span.in_scope(|| {
tracing::info!("result = {}", result);
});
}
async fn do_async_work() -> i32 {
42
}in_scope enforces sync-only scope, preventing async misuse.
use tracing::{span, Level};
fn main() {
let outer = span!(Level::INFO, "outer");
let inner = span!(Level::INFO, "inner");
let _outer = outer.enter();
tracing::info!("in outer span");
{
let _inner = inner.enter();
tracing::info!("in inner span");
// Inner is now the current span
}
// Back to outer span
tracing::info!("back in outer span");
}enter allows nested spans with explicit guard management.
use tracing::{span, Level};
fn main() {
let outer = span!(Level::INFO, "outer");
let inner = span!(Level::INFO, "inner");
outer.in_scope(|| {
tracing::info!("in outer span");
inner.in_scope(|| {
tracing::info!("in inner span");
// Inner is the current span
});
tracing::info!("back in outer span");
});
}in_scope nesting is more explicit through closure structure.
use tracing::{span, Level};
fn main() {
let span = span!(Level::INFO, "shared_span");
let span_clone = span.clone();
// Both references enter the same span
span.in_scope(|| {
tracing::info!("from original");
});
span_clone.in_scope(|| {
tracing::info!("from clone");
});
// Spans are reference-counted
}Both enter and in_scope work with cloned spans referencing the same span.
use tracing::{span, Level};
fn main() {
let span = span!(Level::INFO, "request", id = 42, method = "GET");
span.in_scope(|| {
tracing::info!("processing request");
// id and method are part of the span context
});
// Can add fields dynamically
let _enter = span.enter();
span.record("status", "200");
tracing::info!("request completed");
}Both methods support spans with fields; enter allows field updates during the scope.
use tracing::{span, Level};
fn risky_operation() -> Result<(), &'static str> {
Err("something went wrong")
}
fn main() {
let span = span!(Level::INFO, "operation");
// With enter, need explicit cleanup
let _enter = span.enter();
let result = risky_operation();
if result.is_err() {
tracing::error!("operation failed");
}
drop(_enter); // Explicit cleanup
// With in_scope, span exits even on panic
span.in_scope(|| {
risky_operation().unwrap(); // Panics
// Span still exits properly
});
}in_scope guarantees span exit even on panic within the closure.
use tracing::{span, Level};
fn compute() -> i32 {
let span = span!(Level::INFO, "compute");
// in_scope can return values
let result = span.in_scope(|| {
tracing::info!("computing");
42
});
result
}
fn main() {
let value = compute();
println!("Result: {}", value);
}in_scope returns the closure's return value.
use tracing::{span, Level, Instrument};
use tracing::instrument;
// Using instrument attribute for async functions
#[instrument]
async fn process_request(id: u32) -> String {
// Automatically creates span and instruments
tracing::info!("processing");
format!("processed-{}", id)
}
// Manual instrumentation with in_scope
fn sync_process(id: u32) -> String {
let span = span!(Level::INFO, "sync_process", id);
span.in_scope(|| {
tracing::info!("processing");
format!("processed-{}", id)
})
}
#[tokio::main]
async fn main() {
// Async: use instrument
let result = process_request(1).await;
println!("{}", result);
// Sync: use in_scope or enter
let result = sync_process(2);
println!("{}", result);
}The #[instrument] attribute is often cleaner for functions.
use tracing::{span, Level};
use std::thread;
fn main() {
let span = span!(Level::INFO, "shared_span");
// enter guard is not Send
// let _enter = span.enter();
// thread::spawn(move || { ... }); // Won't compile
// in_scope works fine with threading
let span_clone = span.clone();
let handle = thread::spawn(move || {
span_clone.in_scope(|| {
tracing::info!("in thread");
});
});
handle.join().unwrap();
// Can also clone and use in multiple threads
let handles: Vec<_> = (0..4)
.map(|i| {
let span = span.clone();
thread::spawn(move || {
span.in_scope(|| {
tracing::info!(thread_id = i, "working");
});
})
})
.collect();
for h in handles {
h.join().unwrap();
}
}The enter guard cannot be sent across threads; in_scope works naturally with threading.
use tracing::{span, Level};
fn main() {
let span = span!(Level::INFO, "hot_path");
// enter: single guard creation per scope
for _ in 0..1000 {
let _enter = span.enter();
// work
}
// in_scope: closure call overhead per iteration
for _ in 0..1000 {
span.in_scope(|| {
// work
});
}
// Re-entering same span multiple times
let _enter = span.enter();
for _ in 0..1000 {
// All iterations in same span
tracing::info!("iteration");
}
}enter may have slightly less overhead in tight loops, but the difference is negligible.
use tracing::{span, Level, Instrument};
// Library function with span
pub fn process_data(data: &[u8]) -> Vec<u8> {
let span = span!(Level::DEBUG, "process_data", len = data.len());
span.in_scope(|| {
tracing::debug!("processing");
// Process data
data.to_vec()
})
}
// Library async function
pub async fn fetch_data(url: &str) -> Vec<u8> {
let span = span!(Level::DEBUG, "fetch_data", url);
// Use instrument for async
async {
tracing::debug!("fetching");
vec![]
}.instrument(span).await
}Libraries can use in_scope for sync code and instrument for async.
use tracing::{span, Level, Instrument};
fn main() {
let span = span!(Level::INFO, "example");
// Pattern 1: in_scope for sync code
span.in_scope(|| {
tracing::info!("sync work");
});
// Pattern 2: enter for sync code when you need guard control
let _enter = span.enter();
tracing::info!("sync work with guard");
drop(_enter);
// Pattern 3: instrument for async code
// async_work().instrument(span).await;
// Pattern 4: #[instrument] attribute for functions
// #[instrument]
// async fn my_function() { ... }
}Choose the pattern based on sync/async context and scope requirements.
| Aspect | enter | in_scope |
|--------|---------|------------|
| Returns | Guard | Closure result |
| Scope | Guard lifetime | Closure scope |
| Async safe | No (don't hold across await) | Yes (can't hold across await) |
| Send guard | No | N/A |
| Explicit exit | drop(guard) | Automatic |
| Error safety | Manual cleanup | Automatic on panic |
| Closure overhead | No | Yes (slight) |
| Nested spans | Possible | Possible |
| Thread-safe | Guard can't be Send | Works with threads |
enter characteristics:
Sendin_scope characteristics:
.await (enforced by compiler)Use enter when:
Use in_scope when:
Use instrument when:
Key insight: in_scope is safer by design because it cannot accidentally be held across suspension points. The closure-based API enforces correct usage. In async code, neither enter nor in_scope can span await points correctly—use .instrument() for async. The choice between enter and in_scope in sync code is mostly stylistic, but in_scope provides stronger guarantees about scope boundaries and cleanup.