Skip to content

Instantly share code, notes, and snippets.

@ben0x539
Last active August 29, 2015 14:02
Show Gist options
  • Save ben0x539/ebfe2be7068eedd33072 to your computer and use it in GitHub Desktop.
Save ben0x539/ebfe2be7068eedd33072 to your computer and use it in GitHub Desktop.
#![feature(macro_rules)]
use std::task;
use std::any::{Any, AnyRefExt};
use std::io;
use std::iter;
// a channel for transmitting failure context info while unwinding
local_data_key!(diag_tx_key: Sender<FailureContext>)
// trait for extra failure information for the caller,
// kinda like Any but printable
trait Diag: Any {
fn with_msg(&self, |&str|);
}
// copy of Any's impl
impl<'a> AnyRefExt<'a> for &'a Diag {
#[inline]
fn is<T: 'static>(self) -> bool {
use std::intrinsics::TypeId;
// Get TypeId of the type this function is instantiated with
let t = TypeId::of::<T>();
// Get TypeId of the type in the trait object
let boxed = self.get_type_id();
// Compare both TypeIds on equality
t == boxed
}
#[inline]
fn as_ref<T: 'static>(self) -> Option<&'a T> {
use std::mem::{transmute, transmute_copy};
use std::raw::TraitObject;
if self.is::<T>() {
unsafe {
// Get the raw representation of the trait object
let to: TraitObject = transmute_copy(&self);
// Extract the data pointer
Some(transmute(to.data))
}
} else {
None
}
}
}
// basic impls for failure information,
// user-defined implementations would probably be for structs with
// extra fields to extract more details about the failure situation
impl Diag for String {
fn with_msg(&self, f: |&str|) {
f(self.as_slice());
}
}
impl Diag for &'static str {
fn with_msg(&self, f: |&str|) {
f(*self);
}
}
// wrap the above with code location info
struct FailureContext {
file: &'static str,
line: uint,
diag: Box<Diag:Send>,
}
// RAII-invoked dispatcher for FailureContext values
struct Diagnoser<'a> {
diag: ||:'a -> Box<Diag:Send>,
file: &'static str,
line: uint,
}
#[unsafe_destructor]
impl<'a> Drop for Diagnoser<'a> {
fn drop(&mut self) {
if !task::failing() { return; }
for tx in diag_tx_key.get().iter() {
drop(tx.send_opt(FailureContext {
file: self.file,
line: self.line,
diag: (self.diag)(),
}));
}
}
}
// slightly more palatable syntax for wrapping code in a failure context:
// with_diag! { "doing a certain thing": {
// ...
// }}
macro_rules! with_diag {
($d:expr: $code:block) => ({
let _diagnoser = Diagnoser {
diag: || { box () ($d) as Box<Diag:Send> },
file: file!(),
line: line!()
};
$code
})
}
// package up the regular Any task failure reason with the received
// context info, in order
struct DiagFailure {
cause: Box<Any:Send>,
diagnostics: Vec<FailureContext>,
}
// encapsulate failure in a task as usual, but read out extra diagnostics
fn task_try_diag<T: Send>(f: proc():Send -> T) -> Result<T, DiagFailure> {
let (tx, rx) = std::comm::channel();
let result = task::try(proc() {
diag_tx_key.replace(Some(tx));
f()
});
result.map_err(|cause| {
DiagFailure {
cause: cause,
diagnostics: rx.iter().collect(),
}
})
}
// some basic, generic output
fn report_failure(f: &DiagFailure) {
let &DiagFailure { ref cause, ref diagnostics } = f;
io::print("failure in child task: ");
let cause_msg: Option<&&'static str> = cause.as_ref();
for &s in cause_msg.iter() { io::print(*s); }
let cause_msg: Option<&String> = cause.as_ref();
for &s in cause_msg.iter() { io::print(s.as_slice()); }
io::print("\n");
for context in diagnostics.iter() {
let (file, line) = (context.file, context.line);
context.diag.with_msg(|s| println!(" {}:{}: {}", file, line, s));
}
}
struct TypeFilter<T, I> {
inner: I
}
fn type_filter<'a, T: 'static, I: iter::Iterator<&'a Box<Diag:Send>>>(i: I) -> TypeFilter<T, I> {
TypeFilter { inner: i }
}
impl<'a, T: 'static, I: iter::Iterator<&'a Box<Diag:Send>>> Iterator<&'a T> for TypeFilter<T, I> {
fn next(&mut self) -> Option<&'a T> {
loop {
let any = match self.inner.next() {
None => break,
Some(any) => any,
};
let opt_t: Option<&'a T> = any.as_ref();
let r = match opt_t {
None => continue,
Some(r) => r,
};
return Some(r);
}
None
}
}
// ---
// sample failiing code
fn f() {
io::println("doing some tasky things...");
with_diag! { "inside super important subcomponent code": {
g();
}}
}
fn processing_file(filename: &str) -> WhileProcessingFile {
WhileProcessingFile { filename: filename.to_string() }
}
struct WhileProcessingFile {
filename: String
}
impl Diag for WhileProcessingFile {
fn with_msg(&self, f: |&str|) {
let msg = format!("while processing file `{}`", self.filename);
f(msg.as_slice());
}
}
fn g() {
io::println("still tasking...");
let filename = "test.png";
with_diag! { processing_file(filename): {
let mut file = io::File::open(&Path::new(filename)).unwrap();
let contents = file.read_to_end().unwrap();
// produce unhelpful "unwrapping None" failure message
let clearly_its_all_text =
std::str::from_utf8(contents.as_slice()).unwrap();
io::println(clearly_its_all_text);
// ...
}}
}
fn main() {
let result = task_try_diag(proc() {
f();
io::println("hooray, success?");
});
io::println("back in main task");
for d in result.err().iter() {
report_failure(d);
for context in type_filter(d.diagnostics.iter().map(|ctxt| &ctxt.diag)) {
let context: &WhileProcessingFile = context;
println!("clearly the failure is related to file `{}`!",
context.filename);
}
}
}
doing some tasky things...
still tasking...
task '<unnamed>' failed at 'called `Option::unwrap()` on a `None` value', [...]/rust/src/libcore/option.rs:265
back in main task
failure in child task: called `Option::unwrap()` on a `None` value
diag.rs:202: while processing file `test.png`
diag.rs:181: inside super important subcomponent code
clearly the failure is related to file `test.png`!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment