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
102impl Error {
103 /// Construct an incompatible-shapes validation error.
104 ///
105 /// # Examples
106 ///
107 /// ```rust
108 /// use tenferro_tensor::Error;
109 ///
110 /// let error = Error::shape_mismatch("add", [2, 3], [2, 4]);
111 /// assert!(matches!(error, Error::Validation { .. }));
112 /// ```
113 pub fn shape_mismatch(
114 op: &'static str,
115 lhs: impl Into<Vec<usize>>,
116 rhs: impl Into<Vec<usize>>,
117 ) -> Self {
118 Self::validation(
119 op,
120 tenferro_tensor_core::ShapeMismatch::IncompatibleShapes {
121 lhs: tenferro_tensor_core::ShapeVec::from_vec(lhs.into()),
122 rhs: tenferro_tensor_core::ShapeVec::from_vec(rhs.into()),
123 }
124 .into(),
125 )
126 }
127
128 /// Construct a rank-mismatch validation error.
129 ///
130 /// # Examples
131 ///
132 /// ```rust
133 /// use tenferro_tensor::Error;
134 ///
135 /// let error = Error::rank_mismatch("transpose", 2, 3);
136 /// assert!(matches!(error, Error::Validation { .. }));
137 /// ```
138 pub fn rank_mismatch(op: &'static str, expected: usize, actual: usize) -> Self {
139 Self::validation(op, ValidationError::RankMismatch { expected, actual })
140 }
141
142 /// Construct an axis-out-of-bounds validation error.
143 ///
144 /// # Examples
145 ///
146 /// ```rust
147 /// use tenferro_tensor::Error;
148 ///
149 /// let error = Error::axis_out_of_bounds("sum", 2, 2);
150 /// assert!(matches!(error, Error::Validation { .. }));
151 /// ```
152 pub fn axis_out_of_bounds(op: &'static str, axis: usize, rank: usize) -> Self {
153 Self::validation(op, ValidationError::AxisOutOfBounds { axis, rank })
154 }
155
156 /// Construct a duplicate-axis validation error.
157 ///
158 /// # Examples
159 ///
160 /// ```rust
161 /// use tenferro_tensor::Error;
162 ///
163 /// let error = Error::duplicate_axis("transpose", 1, "permutation");
164 /// assert!(matches!(error, Error::Validation { .. }));
165 /// ```
166 pub fn duplicate_axis(op: &'static str, axis: usize, role: &'static str) -> Self {
167 Self::validation(op, ValidationError::DuplicateAxis { axis, role })
168 }
169
170 /// Construct a dtype-mismatch validation error.
171 ///
172 /// # Examples
173 ///
174 /// ```rust
175 /// use tenferro_tensor::{DType, Error};
176 ///
177 /// let error = Error::dtype_mismatch("add", DType::F32, DType::F64);
178 /// assert!(matches!(error, Error::Validation { .. }));
179 /// ```
180 pub fn dtype_mismatch(op: &'static str, expected: crate::DType, actual: crate::DType) -> Self {
181 Self::validation(
182 op,
183 ValidationError::DTypeMismatch {
184 expected: crate::core_dtype(expected),
185 actual: crate::core_dtype(actual),
186 },
187 )
188 }
189
190 /// Wrap shared tensor validation with the operation that requested it.
191 ///
192 /// # Examples
193 ///
194 /// ```rust
195 /// use tenferro_tensor::{Error, ValidationError};
196 ///
197 /// let error = Error::validation(
198 /// "transpose",
199 /// ValidationError::AxisOutOfBounds { axis: 2, rank: 2 },
200 /// );
201 /// assert!(matches!(error, Error::Validation { op: "transpose", .. }));
202 /// ```
203 pub fn validation(op: &'static str, source: ValidationError) -> Self {
204 Self::Validation { op, source }
205 }
206
207 /// Construct a structured invalid-argument validation error.
208 ///
209 /// # Examples
210 ///
211 /// ```rust
212 /// use tenferro_tensor::{Error, ErrorKind, ValidationKind};
213 ///
214 /// let error = Error::invalid_argument("slice", "step", "must be non-zero");
215 /// assert_eq!(error.kind(), ErrorKind::Validation(ValidationKind::InvalidArgument));
216 /// ```
217 pub fn invalid_argument(
218 op: &'static str,
219 argument: &'static str,
220 message: impl Into<String>,
221 ) -> Self {
222 Self::validation(
223 op,
224 ValidationError::InvalidArgument {
225 argument,
226 message: message.into(),
227 },
228 )
229 }
230
231 /// Construct an unsupported dtype conversion error.
232 ///
233 /// # Examples
234 ///
235 /// ```rust
236 /// let error = tenferro_tensor::Error::unsupported_dtype_conversion(
237 /// "convert",
238 /// tenferro_tensor::DType::F64,
239 /// tenferro_tensor::DType::I32,
240 /// "lossy conversion is disabled",
241 /// );
242 /// assert!(matches!(
243 /// error,
244 /// tenferro_tensor::Error::UnsupportedDTypeConversion { .. }
245 /// ));
246 /// ```
247 pub fn unsupported_dtype_conversion(
248 op: &'static str,
249 from: crate::DType,
250 to: crate::DType,
251 message: impl Into<String>,
252 ) -> Self {
253 Self::UnsupportedDTypeConversion {
254 op,
255 from,
256 to,
257 message: message.into(),
258 }
259 }
260
261 /// Construct an operation-level unsupported-dtype error.
262 ///
263 /// This is for an operation that cannot run for the supplied dtype. It is
264 /// deliberately distinct from [`Error::unsupported_dtype_conversion`],
265 /// which is reserved for an actual from-dtype to to-dtype conversion.
266 ///
267 /// # Examples
268 ///
269 /// ```rust
270 /// let error = tenferro_tensor::Error::unsupported_dtype(
271 /// "exp",
272 /// tenferro_tensor::DType::I64,
273 /// "integer exponentials are not implemented",
274 /// );
275 /// assert!(matches!(
276 /// error,
277 /// tenferro_tensor::Error::UnsupportedDType {
278 /// op: "exp",
279 /// dtype: tenferro_tensor::DType::I64,
280 /// ..
281 /// }
282 /// ));
283 /// ```
284 pub fn unsupported_dtype(
285 op: &'static str,
286 dtype: crate::DType,
287 message: impl Into<String>,
288 ) -> Self {
289 Self::UnsupportedDType {
290 op,
291 dtype,
292 message: message.into(),
293 }
294 }
295
296 /// Construct a structured unsupported-operation error.
297 ///
298 /// Use this for an operation or execution surface that is not implemented
299 /// by the selected backend. Dtype conversion failures use
300 /// [`Error::unsupported_dtype_conversion`] instead, and operation-specific
301 /// typed reasons should use [`Error::extension`] with `ErrorKind::Unsupported`.
302 ///
303 /// # Examples
304 ///
305 /// ```rust
306 /// let error = tenferro_tensor::Error::unsupported(
307 /// "full_piv_lu",
308 /// "backend has no implementation",
309 /// );
310 /// assert!(matches!(
311 /// error,
312 /// tenferro_tensor::Error::Unsupported { op: "full_piv_lu", .. }
313 /// ));
314 /// ```
315 pub fn unsupported(op: &'static str, message: impl Into<String>) -> Self {
316 Self::Unsupported {
317 op,
318 message: message.into(),
319 }
320 }
321
322 /// Construct a text-only backend failure.
323 ///
324 /// Use [`Error::backend_source`] when a typed source is available.
325 ///
326 /// # Examples
327 ///
328 /// ```rust
329 /// let error = tenferro_tensor::Error::backend_failure(
330 /// "matmul",
331 /// "backend rejected launch",
332 /// );
333 /// assert!(matches!(
334 /// error,
335 /// tenferro_tensor::Error::BackendFailure { op: "matmul", .. }
336 /// ));
337 /// ```
338 pub fn backend_failure(op: &'static str, message: impl Into<String>) -> Self {
339 Self::BackendFailure {
340 op,
341 message: message.into(),
342 }
343 }
344
345 /// Construct a backend failure while preserving its typed source.
346 ///
347 /// # Examples
348 ///
349 /// ```rust
350 /// let error = tenferro_tensor::Error::backend_source(
351 /// "load",
352 /// std::io::Error::other("read failed"),
353 /// );
354 /// assert!(std::error::Error::source(&error).is_some());
355 /// ```
356 pub fn backend_source<E>(op: &'static str, source: E) -> Self
357 where
358 E: StdError + Send + Sync + 'static,
359 {
360 Self::BackendSource {
361 op,
362 source: Box::new(source),
363 }
364 }
365
366 /// Construct an I/O failure while preserving its typed source.
367 ///
368 /// I/O errors are intentionally separate from backend failures: callers
369 /// can classify them as [`ErrorKind::Io`] without parsing a message.
370 ///
371 /// # Examples
372 ///
373 /// ```rust
374 /// use tenferro_tensor::{Error, ErrorKind};
375 ///
376 /// let error = Error::io_source("load", std::io::Error::other("read failed"));
377 /// assert_eq!(error.kind(), ErrorKind::Io);
378 /// assert!(std::error::Error::source(&error).is_some());
379 /// ```
380 pub fn io_source<E>(op: &'static str, source: E) -> Self
381 where
382 E: StdError + Send + Sync + 'static,
383 {
384 Self::IoSource {
385 op,
386 source: Box::new(source),
387 }
388 }
389
390 /// Construct a runtime-state failure when no typed source exists.
391 ///
392 /// Use this for missing, uninitialized, or invalid execution state. It is
393 /// distinct from [`Error::backend_failure`], which is reserved for
394 /// vendor/backend status text.
395 ///
396 /// # Examples
397 ///
398 /// ```rust
399 /// use tenferro_tensor::{Error, ErrorKind};
400 ///
401 /// let error = Error::runtime_state("execute", "backend session is not initialized");
402 /// assert_eq!(error.kind(), ErrorKind::RuntimeState);
403 /// ```
404 pub fn runtime_state(op: &'static str, message: impl Into<String>) -> Self {
405 Self::RuntimeState {
406 op,
407 message: message.into(),
408 }
409 }
410
411 /// Construct a runtime-state failure while preserving a typed source.
412 ///
413 /// # Examples
414 ///
415 /// ```rust
416 /// use tenferro_tensor::{Error, ErrorKind};
417 ///
418 /// let error = Error::runtime_state_source(
419 /// "execute",
420 /// std::io::Error::other("executor lock poisoned"),
421 /// );
422 /// assert_eq!(error.kind(), ErrorKind::RuntimeState);
423 /// assert!(std::error::Error::source(&error).is_some());
424 /// ```
425 pub fn runtime_state_source<E>(op: &'static str, source: E) -> Self
426 where
427 E: StdError + Send + Sync + 'static,
428 {
429 Self::RuntimeStateSource {
430 op,
431 source: Box::new(source),
432 }
433 }
434
435 /// Construct an extension failure while preserving its typed source and
436 /// coarse classification.
437 ///
438 /// # Examples
439 ///
440 /// ```rust
441 /// use std::error::Error as _;
442 /// use tenferro_tensor::{Error, ErrorKind};
443 ///
444 /// let error = Error::extension(
445 /// "einsum",
446 /// "einsum",
447 /// ErrorKind::Internal,
448 /// std::io::Error::other("planner failed"),
449 /// );
450 /// assert!(error.source().is_some());
451 /// ```
452 pub fn extension<E>(op: &'static str, family: &'static str, kind: ErrorKind, source: E) -> Self
453 where
454 E: StdError + Send + Sync + 'static,
455 {
456 Self::Extension {
457 op,
458 family,
459 kind,
460 source: Box::new(source),
461 }
462 }
463
464 /// Preserve a typed guarded-host-access failure.
465 ///
466 /// # Examples
467 ///
468 /// ```rust
469 /// use tenferro_tensor::{Error, HostAccessError};
470 ///
471 /// let error = Error::host_access(
472 /// "map",
473 /// HostAccessError::Unsupported { backend: "opaque" },
474 /// );
475 /// assert!(matches!(error, Error::HostAccess { .. }));
476 /// ```
477 pub fn host_access(op: &'static str, source: crate::HostAccessError) -> Self {
478 Self::HostAccess { op, source }
479 }
480
481 /// Return the stable coarse classification for this tensor failure.
482 ///
483 /// # Examples
484 ///
485 /// ```rust
486 /// use tenferro_tensor::{Error, ErrorKind, ValidationError, ValidationKind};
487 /// use tenferro_tensor::core::DType;
488 ///
489 /// let error = Error::validation(
490 /// "add",
491 /// ValidationError::DTypeMismatch {
492 /// expected: DType::F32,
493 /// actual: DType::F64,
494 /// },
495 /// );
496 /// assert_eq!(error.kind(), ErrorKind::Validation(ValidationKind::DTypeMismatch));
497 /// ```
498 pub fn kind(&self) -> ErrorKind {
499 match self {
500 Self::Validation { source, .. } => ErrorKind::Validation(source.kind()),
501 Self::UnsupportedDTypeConversion { .. }
502 | Self::UnsupportedDType { .. }
503 | Self::Unsupported { .. } => ErrorKind::Unsupported,
504 Self::BackendFailure { .. } | Self::BackendSource { .. } => ErrorKind::BackendFailure,
505 Self::IoSource { .. } => ErrorKind::Io,
506 Self::RuntimeState { .. }
507 | Self::RuntimeStateSource { .. }
508 | Self::HostAccess { .. } => ErrorKind::RuntimeState,
509 Self::Extension { kind, .. } => *kind,
510 Self::MissingValue { .. } => ErrorKind::RuntimeState,
511 Self::Internal(_) => ErrorKind::Internal,
512 }
513 }
514}
515
516/// Result type alias for runtime tensor operations.
517pub type Result<T> = std::result::Result<T, Error>;