spinoso_exception/lib.rs
1#![warn(clippy::all, clippy::pedantic, clippy::undocumented_unsafe_blocks)]
2#![allow(
3 clippy::let_underscore_untyped,
4 reason = "https://github.com/rust-lang/rust-clippy/pull/10442#issuecomment-1516570154"
5)]
6#![allow(
7 clippy::question_mark,
8 reason = "https://github.com/rust-lang/rust-clippy/issues/8281"
9)]
10#![allow(clippy::manual_let_else, reason = "manual_let_else was very buggy on release")]
11#![allow(clippy::missing_errors_doc, reason = "A lot of existing code fails this lint")]
12#![allow(
13 clippy::unnecessary_lazy_evaluations,
14 reason = "https://github.com/rust-lang/rust-clippy/issues/8109"
15)]
16#![cfg_attr(
17 test,
18 allow(clippy::non_ascii_literal, reason = "tests sometimes require UTF-8 string content")
19)]
20#![allow(unknown_lints)]
21#![warn(
22 missing_copy_implementations,
23 missing_debug_implementations,
24 missing_docs,
25 rust_2024_compatibility,
26 trivial_casts,
27 trivial_numeric_casts,
28 unused_qualifications,
29 variant_size_differences
30)]
31#![forbid(unsafe_code)]
32// Enable feature callouts in generated documentation:
33// https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html
34//
35// This approach is borrowed from tokio.
36#![cfg_attr(docsrs, feature(doc_cfg))]
37#![cfg_attr(docsrs, feature(doc_alias))]
38
39//! Built in Ruby exception types.
40//!
41//! Descendants of class [`Exception`] are used to communicate between
42//! [`Kernel#raise`] and `rescue` statements in `begin ... end` blocks.
43//! Exception objects carry information about the exception – its type (the
44//! exception's class name), an optional descriptive string, and optional
45//! traceback information. `Exception` subclasses may add additional information
46//! like [`NameError#name`].
47//!
48//! # Ruby Exception Hierarchy
49//!
50//! The built-in subclasses of [`Exception`] are:
51//!
52//! - [`NoMemoryError`]
53//! - [`ScriptError`]
54//! - [`LoadError`]
55//! - [`NotImplementedError`]
56//! - [`SyntaxError`]
57//! - [`SecurityError`]
58//! - [`SignalException`]
59//! - [`Interrupt`]
60//! - [`StandardError`] — default for `rescue`
61//! - [`ArgumentError`]
62//! - [`UncaughtThrowError`]
63//! - [`EncodingError`]
64//! - [`FiberError`]
65//! - [`IOError`]
66//! - [`EOFError`]
67//! - [`IndexError`]
68//! - [`KeyError`]
69//! - [`StopIteration`]
70//! - [`LocalJumpError`]
71//! - [`NameError`]
72//! - [`NoMethodError`]
73//! - [`RangeError`]
74//! - [`FloatDomainError`]
75//! - [`RegexpError`]
76//! - [`RuntimeError`] — default for `raise`
77//! - [`FrozenError`]
78//! - [`SystemCallError`]
79//! - `Errno::*`
80//! - [`ThreadError`]
81//! - [`TypeError`]
82//! - [`ZeroDivisionError`]
83//! - [`SystemExit`]
84//! - [`SystemStackError`]
85//! - `fatal` — impossible to rescue
86//!
87//! # `no_std`
88//!
89//! This crate is `no_std` compatible when built without the `std` feature. This
90//! crate has a required dependency on [`alloc`].
91//!
92//! [`Exception`]: https://ruby-doc.org/core-3.1.2/Exception.html
93//! [`Kernel#raise`]: https://ruby-doc.org/core-3.1.2/Kernel.html#method-i-raise
94//! [`NameError#name`]: https://ruby-doc.org/core-3.1.2/NameError.html#method-i-name
95//! [`NoMemoryError`]: https://ruby-doc.org/core-3.1.2/NoMemoryError.html
96//! [`ScriptError`]: https://ruby-doc.org/core-3.1.2/ScriptError.html
97//! [`LoadError`]: https://ruby-doc.org/core-3.1.2/LoadError.html
98//! [`NotImplementedError`]: https://ruby-doc.org/core-3.1.2/NotImplementedError.html
99//! [`SyntaxError`]: https://ruby-doc.org/core-3.1.2/SyntaxError.html
100//! [`SecurityError`]: https://ruby-doc.org/core-3.1.2/SecurityError.html
101//! [`SignalException`]: https://ruby-doc.org/core-3.1.2/SignalException.html
102//! [`Interrupt`]: https://ruby-doc.org/core-3.1.2/Interrupt.html
103//! [`StandardError`]: https://ruby-doc.org/core-3.1.2/StandardError.html
104//! [`ArgumentError`]: https://ruby-doc.org/core-3.1.2/ArgumentError.html
105//! [`UncaughtThrowError`]: https://ruby-doc.org/core-3.1.2/UncaughtThrowError.html
106//! [`EncodingError`]: https://ruby-doc.org/core-3.1.2/EncodingError.html
107//! [`FiberError`]: https://ruby-doc.org/core-3.1.2/FiberError.html
108//! [`IOError`]: https://ruby-doc.org/core-3.1.2/IOError.html
109//! [`EOFError`]: https://ruby-doc.org/core-3.1.2/EOFError.html
110//! [`IndexError`]: https://ruby-doc.org/core-3.1.2/IndexError.html
111//! [`KeyError`]: https://ruby-doc.org/core-3.1.2/KeyError.html
112//! [`StopIteration`]: https://ruby-doc.org/core-3.1.2/StopIteration.html
113//! [`LocalJumpError`]: https://ruby-doc.org/core-3.1.2/LocalJumpError.html
114//! [`NameError`]: https://ruby-doc.org/core-3.1.2/NameError.html
115//! [`NoMethodError`]: https://ruby-doc.org/core-3.1.2/NoMethodError.html
116//! [`RangeError`]: https://ruby-doc.org/core-3.1.2/RangeError.html
117//! [`FloatDomainError`]: https://ruby-doc.org/core-3.1.2/FloatDomainError.html
118//! [`RegexpError`]: https://ruby-doc.org/core-3.1.2/RegexpError.html
119//! [`RuntimeError`]: https://ruby-doc.org/core-3.1.2/RuntimeError.html
120//! [`FrozenError`]: https://ruby-doc.org/core-3.1.2/FrozenError.html
121//! [`SystemCallError`]: https://ruby-doc.org/core-3.1.2/SystemCallError.html
122//! [`ThreadError`]: https://ruby-doc.org/core-3.1.2/ThreadError.html
123//! [`TypeError`]: https://ruby-doc.org/core-3.1.2/TypeError.html
124//! [`ZeroDivisionError`]: https://ruby-doc.org/core-3.1.2/ZeroDivisionError.html
125//! [`SystemExit`]: https://ruby-doc.org/core-3.1.2/SystemExit.html
126//! [`SystemStackError`]: https://ruby-doc.org/core-3.1.2/SystemStackError.html
127
128#![no_std]
129
130// Ensure code blocks in `README.md` compile
131#[cfg(doctest)]
132#[doc = include_str!("../README.md")]
133mod readme {}
134
135extern crate alloc;
136
137use alloc::borrow::Cow;
138
139pub mod core;
140
141#[doc(inline)]
142pub use self::core::*;
143
144/// Polymorphic exception type that corresponds to Ruby's `Exception`.
145///
146/// This trait unifies all concrete exception types defined in this crate and is
147/// [object safe]. This means `RubyException` can be used as a trait object to
148/// represent an error type of any set of exception subclasses.
149///
150/// All types that implement `RubyException` should be `raise`able in an
151/// Artichoke Ruby VM.
152///
153/// # Examples
154///
155/// ```
156/// # use spinoso_exception::*;
157/// # struct Array(()); impl Array { pub fn is_frozen(&self) -> bool { true } }
158/// fn array_concat(slf: Array, other: Array) -> Result<Array, Box<dyn RubyException>> {
159/// if slf.is_frozen() {
160/// return Err(Box::new(FrozenError::new()));
161/// }
162/// Err(Box::new(NotImplementedError::new()))
163/// }
164/// ```
165///
166/// [object safe]: https://doc.rust-lang.org/book/ch17-02-trait-objects.html#object-safety-is-required-for-trait-objects
167pub trait RubyException {
168 /// The exception's message.
169 ///
170 /// # Examples
171 ///
172 /// ```
173 /// # use spinoso_exception::*;
174 /// fn exception_inspect(exc: &dyn RubyException) {
175 /// let message = exc.message();
176 /// let message = String::from_utf8_lossy(&message);
177 /// println!("{} ({})", exc.name(), message);
178 /// }
179 /// ```
180 ///
181 /// # Implementation notes
182 ///
183 /// This method returns a byte slice since Ruby `String`s are best
184 /// represented as a [`Vec<u8>`].
185 ///
186 /// [`Vec<u8>`]: alloc::vec::Vec
187 fn message(&self) -> Cow<'_, [u8]>;
188
189 /// The exception's class name.
190 ///
191 /// # Examples
192 ///
193 /// ```
194 /// # use spinoso_exception::*;
195 /// fn exception_inspect(exc: &dyn RubyException) {
196 /// let message = exc.message();
197 /// let message = String::from_utf8_lossy(&message);
198 /// println!("{} ({})", exc.name(), message);
199 /// }
200 /// ```
201 fn name(&self) -> Cow<'_, str>;
202}
203
204// Assert that `RubyException` is object-safe (i.e. supports dynamic dispatch).
205const _: Option<&dyn RubyException> = None;