第17回:エラーハンドリング(前編)
~Resultシステムの拡張とエラー設計~
はじめに
効果的なエラー処理は、堅牢なシステムの基盤となります。今回はResultシステムの拡張とエラー値の設計について解説します。
Resultシステムの拡張
// 拡張Result型
pub struct ExtendedResult<T, E> {
inner: Result<T, E>,
context: ErrorContext,
default_handler: Option<Box<dyn ErrorHandler<E>>>,
}
impl<T, E> ExtendedResult<T, E> {
pub fn new(result: Result<T, E>) -> Self {
Self {
inner: result,
context: ErrorContext::new(),
default_handler: None,
}
}
pub fn with_context(mut self, context: ErrorContext) -> Self {
self.context = context;
self
}
pub fn with_default_handler<H: ErrorHandler<E> + 'static>(
mut self,
handler: H,
) -> Self {
self.default_handler = Some(Box::new(handler));
self
}
pub fn unwrap_or_handle(self) -> Result<T, E> {
match self.inner {
Ok(value) => Ok(value),
Err(error) => {
if let Some(handler) = self.default_handler {
handler.handle(error, &self.context)
} else {
Err(error)
}
}
}
}
}
// エラーコンテキスト
#[derive(Clone, Debug)]
pub struct ErrorContext {
location: Location,
timestamp: SystemTime,
trace: Backtrace,
metadata: HashMap<String, Value>,
}
impl ErrorContext {
pub fn add_metadata<K, V>(&mut self, key: K, value: V)
where
K: Into<String>,
V: Into<Value>,
{
self.metadata.insert(key.into(), value.into());
}
}
デフォルトエラー値の設計
// デフォルトエラー値の定義
#[derive(Debug, Clone)]
pub enum SystemDefault<E> {
Value(E),
Handler(Box<dyn Fn() -> E + Send + Sync>),
}
// エラー型の拡張トレイト
pub trait ErrorExt: Sized {
fn with_default(self, default: SystemDefault<Self>) -> DefaultError<Self>;
}
// デフォルトエラーラッパー
pub struct DefaultError<E> {
inner: Option<E>,
default: SystemDefault<E>,
}
impl<E> DefaultError<E> {
pub fn unwrap(self) -> E {
match self.inner {
Some(error) => error,
None => match &self.default {
SystemDefault::Value(ref value) => value.clone(),
SystemDefault::Handler(handler) => handler(),
},
}
}
}
// エラー変換機構
pub trait ErrorConverter<From, To> {
fn convert(&self, from: From) -> To;
}
pub struct DefaultErrorConverter<From, To> {
converter: Box<dyn Fn(From) -> To + Send + Sync>,
}
impl<From, To> DefaultErrorConverter<From, To> {
pub fn new<F>(converter: F) -> Self
where
F: Fn(From) -> To + Send + Sync + 'static,
{
Self {
converter: Box::new(converter),
}
}
}
エラー変換機構
// エラー変換システム
pub struct ErrorConversionSystem {
converters: HashMap<(TypeId, TypeId), Box<dyn Any + Send + Sync>>,
}
impl ErrorConversionSystem {
pub fn register_converter<From, To>(
&mut self,
converter: DefaultErrorConverter<From, To>,
)
where
From: 'static,
To: 'static,
{
let key = (TypeId::of::<From>(), TypeId::of::<To>());
self.converters.insert(key, Box::new(converter));
}
pub fn convert<From, To>(&self, from: From) -> Result<To, ConversionError>
where
From: 'static,
To: 'static,
{
let key = (TypeId::of::<From>(), TypeId::of::<To>());
if let Some(converter) = self.converters.get(&key) {
if let Some(converter) = converter.downcast_ref::<DefaultErrorConverter<From, To>>() {
Ok((converter.converter)(from))
} else {
Err(ConversionError::InvalidConverter)
}
} else {
Err(ConversionError::NoConverter)
}
}
}
// マクロによる便利な構文
#[macro_export]
macro_rules! with_default {
($result:expr, $default:expr) => {
ExtendedResult::new($result).with_default_handler(|e| Ok($default))
};
}
実装例:カスタムエラー型の実装
// カスタムエラー型の実装例
#[derive(Debug, Clone)]
pub enum AppError {
Database(DatabaseError),
Validation(ValidationError),
System(SystemError),
#[default]
Unknown,
}
impl AppError {
pub fn as_database(&self) -> Option<&DatabaseError> {
match self {
Self::Database(err) => Some(err),
_ => None,
}
}
pub fn is_critical(&self) -> bool {
matches!(self, Self::System(_))
}
}
// エラーハンドリングシステム
pub struct ErrorHandlingSystem {
conversion_system: ErrorConversionSystem,
default_handlers: HashMap<TypeId, Box<dyn ErrorHandler<AppError>>>,
}
impl ErrorHandlingSystem {
pub fn handle_error<E: Into<AppError>>(&self, error: E) -> Result<(), AppError> {
let app_error = error.into();
// クリティカルエラーの特別処理
if app_error.is_critical() {
return Err(app_error);
}
// デフォルトハンドラーの適用
if let Some(handler) = self.get_handler(&app_error) {
handler.handle(app_error, &ErrorContext::new())
} else {
Err(app_error)
}
}
}
// 使用例
async fn example_usage() -> Result<(), AppError> {
let mut system = ErrorHandlingSystem::new();
// データベース操作
let result = database_operation()
.map_err(AppError::Database)
.with_default(SystemDefault::Value(AppError::Unknown));
match result {
Ok(value) => {
// 正常処理
Ok(())
}
Err(error) => {
// エラー処理
system.handle_error(error)
}
}
}
// デフォルト値を使用した例
fn process_with_default<T>(
operation: impl FnOnce() -> Result<T, AppError>,
default: T,
) -> T {
with_default!(operation(), default).unwrap_or_handle().unwrap_or(default)
}
今回のまとめ
- 柔軟なResultシステムの拡張
- 効果的なデフォルトエラー値の設計
- 型安全なエラー変換機構
- 実用的なエラーハンドリングシステム
次回予告
第18回では、エラーハンドリングの後編として、エラーの伝播制御とリカバリーメカニズムについて解説します。
参考資料
- Error Handling in Rust
- Type-Safe Error Management
- Error Recovery Patterns
- Resilient System Design