tenferro_tensor/error.rs
1//! Runtime error types for tensor execution.
2//!
3//! # Examples
4//!
5//! ```rust
6//! let error = tenferro_tensor::Error::shape_mismatch("add", [2], [3]);
7//! assert!(matches!(
8//! error,
9//! tenferro_tensor::Error::Validation { op: "add", .. }
10//! ));
11//! ```
12
13use std::error::Error as StdError;
14
15use tenferro_tensor_core::{ErrorKind, ValidationError};
16
17/// Boxed source used for backend and extension failures whose concrete type is
18/// owned by another crate or a vendor API.
19pub type BoxError = Box<dyn StdError + Send + Sync + 'static>;
20
21/// Runtime failures produced by tensor execution backends and helpers.
22///
23/// Validation failures retain the shared tensor-core payload as a typed source.
24/// Backend and extension failures retain opaque typed sources when one exists;
25/// text-only vendor failures use [`Error::BackendFailure`].
26///
27/// # Examples
28///
29/// ```rust
30/// let error = tenferro_tensor::Error::rank_mismatch("reshape", 2, 1);
31/// assert!(matches!(
32/// error,
33/// tenferro_tensor::Error::Validation { op: "reshape", .. }
34/// ));
35/// ```
36#[derive(Debug, thiserror::Error)]
37#[non_exhaustive]
38pub enum Error {
39 #[error("{op}: {source}")]
40 Validation {
41 op: &'static str,
42 #[source]
43 source: ValidationError,
44 },
45 #[error("{op}: unsupported dtype conversion from {from:?} to {to:?}: {message}")]
46 UnsupportedDTypeConversion {
47 op: &'static str,
48 from: crate::DType,
49 to: crate::DType,
50 message: String,
51 },
52 #[error("{op}: unsupported dtype {dtype:?}: {message}")]
53 UnsupportedDType {
54 op: &'static str,
55 dtype: crate::DType,
56 message: String,
57 },
58 #[error("{op}: unsupported operation: {message}")]
59 Unsupported { op: &'static str, message: String },
60 #[error("{op}: backend failure: {message}")]
61 BackendFailure { op: &'static str, message: String },
62 #[error("{op}: backend failure: {source}")]
63 BackendSource {
64 op: &'static str,
65 #[source]
66 source: BoxError,
67 },
68 #[error("{op}: I/O failure: {source}")]
69 IoSource {
70 op: &'static str,
71 #[source]
72 source: BoxError,
73 },
74 #[error("{op}: runtime state failure: {message}")]
75 RuntimeState { op: &'static str, message: String },
76 #[error("{op}: runtime state failure: {source}")]
77 RuntimeStateSource {
78 op: &'static str,
79 #[source]
80 source: BoxError,
81 },
82 #[error("{op}: host access failed: {source}")]
83 HostAccess {
84 op: &'static str,
85 #[source]
86 source: crate::HostAccessError,
87 },
88 #[error("{op}: extension {family} failed: {source}")]
89 Extension {
90 op: &'static str,
91 family: &'static str,
92 kind: ErrorKind,
93 #[source]
94 source: BoxError,
95 },
96 #[error("missing runtime value for slot {slot}")]
97 MissingValue { slot: usize },
98 #[error("internal tensor error: {0}")]
99 Internal(String),
100}
101
102/// Owns the original tensor when a consuming representation reinterpretation
103/// cannot publish its checked descriptor.
104///
105/// Reinterpretation never falls back to an allocation or a copy. Call
106/// [`Self::into_owner`] to recover the unchanged input and [`Self::error`] to
107/// inspect the typed failure.
108///
109/// # Examples
110///
111/// ```
112/// use tenferro_tensor::TypedTensor;
113///
114/// let tensor = TypedTensor::<f32>::from_vec_col_major(vec![1], vec![1.0])?;
115/// let Err(failure) = tensor.into_complex() else { return Ok(()); };
116/// assert!(!failure.error().to_string().is_empty());
117/// # Ok::<(), tenferro_tensor::Error>(())
118/// ```
119#[derive(Debug)]
120pub struct ReinterpretError<T> {
121 owner: Box<T>,
122 error: Error,
123}
124
125impl<T> ReinterpretError<T> {
126 pub(crate) fn new(owner: T, error: Error) -> Self {
127 Self {
128 owner: Box::new(owner),
129 error,
130 }
131 }
132
133 /// Recover the unchanged original owner.
134 ///
135 /// # Examples
136 ///
137 /// ```
138 /// use tenferro_tensor::TypedTensor;
139 ///
140 /// let tensor = TypedTensor::<f32>::from_vec_col_major(vec![1], vec![1.0])?;
141 /// let Err(failure) = tensor.into_complex() else { return Ok(()); };
142 /// let _owner = failure.into_owner();
143 /// # Ok::<(), tenferro_tensor::Error>(())
144 /// ```
145 pub fn into_owner(self) -> T {
146 *self.owner
147 }
148
149 /// Borrow the typed failure without consuming the owner.
150 ///
151 /// # Examples
152 ///
153 /// ```
154 /// use tenferro_tensor::TypedTensor;
155 ///
156 /// let tensor = TypedTensor::<f32>::from_vec_col_major(vec![1], vec![1.0])?;
157 /// let Err(failure) = tensor.into_complex() else { return Ok(()); };
158 /// assert!(!failure.error().to_string().is_empty());
159 /// # Ok::<(), tenferro_tensor::Error>(())
160 /// ```
161 pub fn error(&self) -> &Error {
162 &self.error
163 }
164
165 pub(crate) fn into_parts(self) -> (T, Error) {
166 (*self.owner, self.error)
167 }
168}
169
170impl<T: std::fmt::Debug> std::fmt::Display for ReinterpretError<T> {
171 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 write!(formatter, "tensor reinterpretation failed: {}", self.error)
173 }
174}
175
176impl<T: std::fmt::Debug + 'static> std::error::Error for ReinterpretError<T> {}
177
178impl Error {
179 /// Construct an incompatible-shapes validation error.
180 ///
181 /// # Examples
182 ///
183 /// ```rust
184 /// use tenferro_tensor::Error;
185 ///
186 /// let error = Error::shape_mismatch("add", [2, 3], [2, 4]);
187 /// assert!(matches!(error, Error::Validation { .. }));
188 /// ```
189 pub fn shape_mismatch(
190 op: &'static str,
191 lhs: impl Into<Vec<usize>>,
192 rhs: impl Into<Vec<usize>>,
193 ) -> Self {
194 Self::validation(
195 op,
196 tenferro_tensor_core::ShapeMismatch::IncompatibleShapes {
197 lhs: tenferro_tensor_core::ShapeVec::from_vec(lhs.into()),
198 rhs: tenferro_tensor_core::ShapeVec::from_vec(rhs.into()),
199 }
200 .into(),
201 )
202 }
203
204 /// Construct a rank-mismatch validation error.
205 ///
206 /// # Examples
207 ///
208 /// ```rust
209 /// use tenferro_tensor::Error;
210 ///
211 /// let error = Error::rank_mismatch("transpose", 2, 3);
212 /// assert!(matches!(error, Error::Validation { .. }));
213 /// ```
214 pub fn rank_mismatch(op: &'static str, expected: usize, actual: usize) -> Self {
215 Self::validation(op, ValidationError::RankMismatch { expected, actual })
216 }
217
218 /// Construct an axis-out-of-bounds validation error.
219 ///
220 /// # Examples
221 ///
222 /// ```rust
223 /// use tenferro_tensor::Error;
224 ///
225 /// let error = Error::axis_out_of_bounds("sum", 2, 2);
226 /// assert!(matches!(error, Error::Validation { .. }));
227 /// ```
228 pub fn axis_out_of_bounds(op: &'static str, axis: usize, rank: usize) -> Self {
229 Self::validation(op, ValidationError::AxisOutOfBounds { axis, rank })
230 }
231
232 /// Construct a duplicate-axis validation error.
233 ///
234 /// # Examples
235 ///
236 /// ```rust
237 /// use tenferro_tensor::Error;
238 ///
239 /// let error = Error::duplicate_axis("transpose", 1, "permutation");
240 /// assert!(matches!(error, Error::Validation { .. }));
241 /// ```
242 pub fn duplicate_axis(op: &'static str, axis: usize, role: &'static str) -> Self {
243 Self::validation(op, ValidationError::DuplicateAxis { axis, role })
244 }
245
246 /// Construct a dtype-mismatch validation error.
247 ///
248 /// # Examples
249 ///
250 /// ```rust
251 /// use tenferro_tensor::{DType, Error};
252 ///
253 /// let error = Error::dtype_mismatch("add", DType::F32, DType::F64);
254 /// assert!(matches!(error, Error::Validation { .. }));
255 /// ```
256 pub fn dtype_mismatch(op: &'static str, expected: crate::DType, actual: crate::DType) -> Self {
257 Self::validation(
258 op,
259 ValidationError::DTypeMismatch {
260 expected: crate::core_dtype(expected),
261 actual: crate::core_dtype(actual),
262 },
263 )
264 }
265
266 /// Wrap shared tensor validation with the operation that requested it.
267 ///
268 /// # Examples
269 ///
270 /// ```rust
271 /// use tenferro_tensor::{Error, ValidationError};
272 ///
273 /// let error = Error::validation(
274 /// "transpose",
275 /// ValidationError::AxisOutOfBounds { axis: 2, rank: 2 },
276 /// );
277 /// assert!(matches!(error, Error::Validation { op: "transpose", .. }));
278 /// ```
279 pub fn validation(op: &'static str, source: ValidationError) -> Self {
280 Self::Validation { op, source }
281 }
282
283 /// Construct a structured invalid-argument validation error.
284 ///
285 /// # Examples
286 ///
287 /// ```rust
288 /// use tenferro_tensor::{Error, ErrorKind, ValidationKind};
289 ///
290 /// let error = Error::invalid_argument("slice", "step", "must be non-zero");
291 /// assert_eq!(error.kind(), ErrorKind::Validation(ValidationKind::InvalidArgument));
292 /// ```
293 pub fn invalid_argument(
294 op: &'static str,
295 argument: &'static str,
296 message: impl Into<String>,
297 ) -> Self {
298 Self::validation(
299 op,
300 ValidationError::InvalidArgument {
301 argument,
302 message: message.into(),
303 },
304 )
305 }
306
307 /// Construct an unsupported dtype conversion error.
308 ///
309 /// # Examples
310 ///
311 /// ```rust
312 /// let error = tenferro_tensor::Error::unsupported_dtype_conversion(
313 /// "convert",
314 /// tenferro_tensor::DType::F64,
315 /// tenferro_tensor::DType::I32,
316 /// "lossy conversion is disabled",
317 /// );
318 /// assert!(matches!(
319 /// error,
320 /// tenferro_tensor::Error::UnsupportedDTypeConversion { .. }
321 /// ));
322 /// ```
323 pub fn unsupported_dtype_conversion(
324 op: &'static str,
325 from: crate::DType,
326 to: crate::DType,
327 message: impl Into<String>,
328 ) -> Self {
329 Self::UnsupportedDTypeConversion {
330 op,
331 from,
332 to,
333 message: message.into(),
334 }
335 }
336
337 /// Construct an operation-level unsupported-dtype error.
338 ///
339 /// This is for an operation that cannot run for the supplied dtype. It is
340 /// deliberately distinct from [`Error::unsupported_dtype_conversion`],
341 /// which is reserved for an actual from-dtype to to-dtype conversion.
342 ///
343 /// # Examples
344 ///
345 /// ```rust
346 /// let error = tenferro_tensor::Error::unsupported_dtype(
347 /// "exp",
348 /// tenferro_tensor::DType::I64,
349 /// "integer exponentials are not implemented",
350 /// );
351 /// assert!(matches!(
352 /// error,
353 /// tenferro_tensor::Error::UnsupportedDType {
354 /// op: "exp",
355 /// dtype: tenferro_tensor::DType::I64,
356 /// ..
357 /// }
358 /// ));
359 /// ```
360 pub fn unsupported_dtype(
361 op: &'static str,
362 dtype: crate::DType,
363 message: impl Into<String>,
364 ) -> Self {
365 Self::UnsupportedDType {
366 op,
367 dtype,
368 message: message.into(),
369 }
370 }
371
372 /// Construct a structured unsupported-operation error.
373 ///
374 /// Use this for an operation or execution surface that is not implemented
375 /// by the selected backend. Dtype conversion failures use
376 /// [`Error::unsupported_dtype_conversion`] instead, and operation-specific
377 /// typed reasons should use [`Error::extension`] with `ErrorKind::Unsupported`.
378 ///
379 /// # Examples
380 ///
381 /// ```rust
382 /// let error = tenferro_tensor::Error::unsupported(
383 /// "full_piv_lu",
384 /// "backend has no implementation",
385 /// );
386 /// assert!(matches!(
387 /// error,
388 /// tenferro_tensor::Error::Unsupported { op: "full_piv_lu", .. }
389 /// ));
390 /// ```
391 pub fn unsupported(op: &'static str, message: impl Into<String>) -> Self {
392 Self::Unsupported {
393 op,
394 message: message.into(),
395 }
396 }
397
398 /// Construct a text-only backend failure.
399 ///
400 /// Use [`Error::backend_source`] when a typed source is available.
401 ///
402 /// # Examples
403 ///
404 /// ```rust
405 /// let error = tenferro_tensor::Error::backend_failure(
406 /// "matmul",
407 /// "backend rejected launch",
408 /// );
409 /// assert!(matches!(
410 /// error,
411 /// tenferro_tensor::Error::BackendFailure { op: "matmul", .. }
412 /// ));
413 /// ```
414 pub fn backend_failure(op: &'static str, message: impl Into<String>) -> Self {
415 Self::BackendFailure {
416 op,
417 message: message.into(),
418 }
419 }
420
421 /// Construct a backend failure while preserving its typed source.
422 ///
423 /// # Examples
424 ///
425 /// ```rust
426 /// let error = tenferro_tensor::Error::backend_source(
427 /// "load",
428 /// std::io::Error::other("read failed"),
429 /// );
430 /// assert!(std::error::Error::source(&error).is_some());
431 /// ```
432 pub fn backend_source<E>(op: &'static str, source: E) -> Self
433 where
434 E: StdError + Send + Sync + 'static,
435 {
436 Self::BackendSource {
437 op,
438 source: Box::new(source),
439 }
440 }
441
442 /// Construct an I/O failure while preserving its typed source.
443 ///
444 /// I/O errors are intentionally separate from backend failures: callers
445 /// can classify them as [`ErrorKind::Io`] without parsing a message.
446 ///
447 /// # Examples
448 ///
449 /// ```rust
450 /// use tenferro_tensor::{Error, ErrorKind};
451 ///
452 /// let error = Error::io_source("load", std::io::Error::other("read failed"));
453 /// assert_eq!(error.kind(), ErrorKind::Io);
454 /// assert!(std::error::Error::source(&error).is_some());
455 /// ```
456 pub fn io_source<E>(op: &'static str, source: E) -> Self
457 where
458 E: StdError + Send + Sync + 'static,
459 {
460 Self::IoSource {
461 op,
462 source: Box::new(source),
463 }
464 }
465
466 /// Construct a runtime-state failure when no typed source exists.
467 ///
468 /// Use this for missing, uninitialized, or invalid execution state. It is
469 /// distinct from [`Error::backend_failure`], which is reserved for
470 /// vendor/backend status text.
471 ///
472 /// # Examples
473 ///
474 /// ```rust
475 /// use tenferro_tensor::{Error, ErrorKind};
476 ///
477 /// let error = Error::runtime_state("execute", "backend session is not initialized");
478 /// assert_eq!(error.kind(), ErrorKind::RuntimeState);
479 /// ```
480 pub fn runtime_state(op: &'static str, message: impl Into<String>) -> Self {
481 Self::RuntimeState {
482 op,
483 message: message.into(),
484 }
485 }
486
487 /// Construct a runtime-state failure while preserving a typed source.
488 ///
489 /// # Examples
490 ///
491 /// ```rust
492 /// use tenferro_tensor::{Error, ErrorKind};
493 ///
494 /// let error = Error::runtime_state_source(
495 /// "execute",
496 /// std::io::Error::other("executor lock poisoned"),
497 /// );
498 /// assert_eq!(error.kind(), ErrorKind::RuntimeState);
499 /// assert!(std::error::Error::source(&error).is_some());
500 /// ```
501 pub fn runtime_state_source<E>(op: &'static str, source: E) -> Self
502 where
503 E: StdError + Send + Sync + 'static,
504 {
505 Self::RuntimeStateSource {
506 op,
507 source: Box::new(source),
508 }
509 }
510
511 /// Construct an extension failure while preserving its typed source and
512 /// coarse classification.
513 ///
514 /// # Examples
515 ///
516 /// ```rust
517 /// use std::error::Error as _;
518 /// use tenferro_tensor::{Error, ErrorKind};
519 ///
520 /// let error = Error::extension(
521 /// "einsum",
522 /// "einsum",
523 /// ErrorKind::Internal,
524 /// std::io::Error::other("planner failed"),
525 /// );
526 /// assert!(error.source().is_some());
527 /// ```
528 pub fn extension<E>(op: &'static str, family: &'static str, kind: ErrorKind, source: E) -> Self
529 where
530 E: StdError + Send + Sync + 'static,
531 {
532 Self::Extension {
533 op,
534 family,
535 kind,
536 source: Box::new(source),
537 }
538 }
539
540 /// Preserve a typed guarded-host-access failure.
541 ///
542 /// # Examples
543 ///
544 /// ```rust
545 /// use tenferro_tensor::{Error, HostAccessError};
546 ///
547 /// let error = Error::host_access(
548 /// "map",
549 /// HostAccessError::Unsupported { backend: "opaque" },
550 /// );
551 /// assert!(matches!(error, Error::HostAccess { .. }));
552 /// ```
553 pub fn host_access(op: &'static str, source: crate::HostAccessError) -> Self {
554 Self::HostAccess { op, source }
555 }
556
557 /// Return the stable coarse classification for this tensor failure.
558 ///
559 /// # Examples
560 ///
561 /// ```rust
562 /// use tenferro_tensor::{Error, ErrorKind, ValidationError, ValidationKind};
563 /// use tenferro_tensor::core::DType;
564 ///
565 /// let error = Error::validation(
566 /// "add",
567 /// ValidationError::DTypeMismatch {
568 /// expected: DType::F32,
569 /// actual: DType::F64,
570 /// },
571 /// );
572 /// assert_eq!(error.kind(), ErrorKind::Validation(ValidationKind::DTypeMismatch));
573 /// ```
574 pub fn kind(&self) -> ErrorKind {
575 match self {
576 Self::Validation { source, .. } => ErrorKind::Validation(source.kind()),
577 Self::UnsupportedDTypeConversion { .. }
578 | Self::UnsupportedDType { .. }
579 | Self::Unsupported { .. } => ErrorKind::Unsupported,
580 Self::BackendFailure { .. } | Self::BackendSource { .. } => ErrorKind::BackendFailure,
581 Self::IoSource { .. } => ErrorKind::Io,
582 Self::RuntimeState { .. }
583 | Self::RuntimeStateSource { .. }
584 | Self::HostAccess { .. } => ErrorKind::RuntimeState,
585 Self::Extension { kind, .. } => *kind,
586 Self::MissingValue { .. } => ErrorKind::RuntimeState,
587 Self::Internal(_) => ErrorKind::Internal,
588 }
589 }
590}
591
592/// Result type alias for runtime tensor operations.
593pub type Result<T> = std::result::Result<T, Error>;