core/panic/
location.rs

1#[cfg(not(feature = "ferrocene_subset"))]
2use crate::cmp::Ordering;
3#[cfg(not(feature = "ferrocene_subset"))]
4use crate::ffi::CStr;
5#[cfg(not(feature = "ferrocene_subset"))]
6use crate::fmt;
7#[cfg(not(feature = "ferrocene_subset"))]
8use crate::hash::{Hash, Hasher};
9use crate::marker::PhantomData;
10use crate::ptr::NonNull;
11
12/// A struct containing information about the location of a panic.
13///
14/// This structure is created by [`PanicHookInfo::location()`] and [`PanicInfo::location()`].
15///
16/// [`PanicInfo::location()`]: crate::panic::PanicInfo::location
17/// [`PanicHookInfo::location()`]: ../../std/panic/struct.PanicHookInfo.html#method.location
18///
19/// # Examples
20///
21/// ```should_panic
22/// use std::panic;
23///
24/// panic::set_hook(Box::new(|panic_info| {
25///     if let Some(location) = panic_info.location() {
26///         println!("panic occurred in file '{}' at line {}", location.file(), location.line());
27///     } else {
28///         println!("panic occurred but can't get location information...");
29///     }
30/// }));
31///
32/// panic!("Normal panic");
33/// ```
34///
35/// # Comparisons
36///
37/// Comparisons for equality and ordering are made in file, line, then column priority.
38/// Files are compared as strings, not `Path`, which could be unexpected.
39/// See [`Location::file`]'s documentation for more discussion.
40#[lang = "panic_location"]
41#[stable(feature = "panic_hooks", since = "1.10.0")]
42#[cfg_attr(not(feature = "ferrocene_subset"), derive(Copy, Clone))]
43pub struct Location<'a> {
44    // A raw pointer is used rather than a reference because the pointer is valid for one more byte
45    // than the length stored in this pointer; the additional byte is the NUL-terminator used by
46    // `Location::file_as_c_str`.
47    filename: NonNull<str>,
48    line: u32,
49    col: u32,
50    _filename: PhantomData<&'a str>,
51}
52
53#[stable(feature = "panic_hooks", since = "1.10.0")]
54#[cfg(not(feature = "ferrocene_subset"))]
55impl PartialEq for Location<'_> {
56    fn eq(&self, other: &Self) -> bool {
57        // Compare col / line first as they're cheaper to compare and more likely to differ,
58        // while not impacting the result.
59        self.col == other.col && self.line == other.line && self.file() == other.file()
60    }
61}
62
63#[stable(feature = "panic_hooks", since = "1.10.0")]
64#[cfg(not(feature = "ferrocene_subset"))]
65impl Eq for Location<'_> {}
66
67#[stable(feature = "panic_hooks", since = "1.10.0")]
68#[cfg(not(feature = "ferrocene_subset"))]
69impl Ord for Location<'_> {
70    fn cmp(&self, other: &Self) -> Ordering {
71        self.file()
72            .cmp(other.file())
73            .then_with(|| self.line.cmp(&other.line))
74            .then_with(|| self.col.cmp(&other.col))
75    }
76}
77
78#[stable(feature = "panic_hooks", since = "1.10.0")]
79#[cfg(not(feature = "ferrocene_subset"))]
80impl PartialOrd for Location<'_> {
81    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
82        Some(self.cmp(other))
83    }
84}
85
86#[stable(feature = "panic_hooks", since = "1.10.0")]
87#[cfg(not(feature = "ferrocene_subset"))]
88impl Hash for Location<'_> {
89    fn hash<H: Hasher>(&self, state: &mut H) {
90        self.file().hash(state);
91        self.line.hash(state);
92        self.col.hash(state);
93    }
94}
95
96#[stable(feature = "panic_hooks", since = "1.10.0")]
97#[cfg(not(feature = "ferrocene_subset"))]
98impl fmt::Debug for Location<'_> {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        f.debug_struct("Location")
101            .field("file", &self.file())
102            .field("line", &self.line)
103            .field("column", &self.col)
104            .finish()
105    }
106}
107
108impl<'a> Location<'a> {
109    /// Returns the source location of the caller of this function. If that function's caller is
110    /// annotated then its call location will be returned, and so on up the stack to the first call
111    /// within a non-tracked function body.
112    ///
113    /// # Examples
114    ///
115    /// ```standalone_crate
116    /// use std::panic::Location;
117    ///
118    /// /// ```
119    /// ///      |1        |11       |21       |31       |41
120    /// ///    +-|---------|---------|---------|---------|--------
121    /// /// 15 | #[track_caller]
122    /// /// 16 | fn new_location() -> &'static Location<'static> {
123    /// /// 17 |     Location::caller()
124    /// ///    |     ------------------| the value of this expression depends on the caller,
125    /// ///    |                       | since the function is marked #[track_caller]
126    /// /// 18 | }
127    /// /// ```
128    /// #[track_caller]
129    /// fn new_location() -> &'static Location<'static> {
130    ///     Location::caller()
131    /// }
132    ///
133    /// /// ```
134    /// ///      |1  |5    |11       |21       |31       |41       |51
135    /// ///    +-|---|-----|---------|---------|---------|---------|---
136    /// /// 29 | fn constant_location() -> &'static Location<'static> {
137    /// /// 30 |     new_location()
138    /// ///    |     ^ any invocation of constant_location() points here,
139    /// ///    |       no matter the location it is called from
140    /// /// 31 | }
141    /// /// ```
142    /// fn constant_location() -> &'static Location<'static> {
143    ///     new_location()
144    /// }
145    ///
146    /// fn main() {
147    ///     //      |1  |5    |11       |21       |31       |41       |51
148    ///     //    +-|---|-----|---------|---------|---------|---------|---
149    ///     // 29 | fn constant_location() -> &'static Location<'static> {
150    ///     // 30 |     new_location()
151    ///     //    |     ^ `let constant` points here
152    ///     // 31 | }
153    ///     let constant = constant_location();
154    ///     assert_eq!(constant.file(), file!());
155    ///     assert_eq!((constant.line(), constant.column()), (30, 5));
156    ///
157    ///     let constant_2 = constant_location();
158    ///     assert_eq!(
159    ///         (constant.file(), constant.line(), constant.column()),
160    ///         (constant_2.file(), constant_2.line(), constant_2.column())
161    ///     );
162    ///
163    ///     //      |1        |11  |16  |21       |31
164    ///     //    +-|---------|----|----|---------|------
165    ///     // 55 |     let here = new_location();
166    ///     //    |                ^ `let here` points here, as `new_location()` is the callsite
167    ///     // 56 |     assert_eq!(here.file(), file!());
168    ///     let here = new_location();
169    ///     assert_eq!(here.file(), file!());
170    ///     assert_eq!((here.line(), here.column()), (55, 16));
171    ///
172    ///     //      |1        |11       |21       ||32      |41       |51
173    ///     //    +-|---------|---------|---------||--------|---------|------
174    ///     // 64 |     let yet_another_location = new_location();
175    ///     //    |                                ^ `let yet_another_location` points here
176    ///     // 65 |     assert_eq!(here.file(), yet_another_location.file());
177    ///     let yet_another_location = new_location();
178    ///     assert_eq!(here.file(), yet_another_location.file());
179    ///     assert_ne!(
180    ///         (here.line(), here.column()),
181    ///         (yet_another_location.line(), yet_another_location.column())
182    ///     );
183    /// }
184    /// ```
185    #[must_use]
186    #[stable(feature = "track_caller", since = "1.46.0")]
187    #[rustc_const_stable(feature = "const_caller_location", since = "1.79.0")]
188    #[track_caller]
189    #[inline]
190    pub const fn caller() -> &'static Location<'static> {
191        crate::intrinsics::caller_location()
192    }
193
194    /// Returns the name of the source file from which the panic originated.
195    ///
196    /// # `&str`, not `&Path`
197    ///
198    /// The returned name refers to a source path on the compiling system, but it isn't valid to
199    /// represent this directly as a `&Path`. The compiled code may run on a different system with
200    /// a different `Path` implementation than the system providing the contents and this library
201    /// does not currently have a different "host path" type.
202    ///
203    /// The most surprising behavior occurs when "the same" file is reachable via multiple paths in
204    /// the module system (usually using the `#[path = "..."]` attribute or similar), which can
205    /// cause what appears to be identical code to return differing values from this function.
206    ///
207    /// # Cross-compilation
208    ///
209    /// This value is not suitable for passing to `Path::new` or similar constructors when the host
210    /// platform and target platform differ.
211    ///
212    /// # Examples
213    ///
214    /// ```should_panic
215    /// use std::panic;
216    ///
217    /// panic::set_hook(Box::new(|panic_info| {
218    ///     if let Some(location) = panic_info.location() {
219    ///         println!("panic occurred in file '{}'", location.file());
220    ///     } else {
221    ///         println!("panic occurred but can't get location information...");
222    ///     }
223    /// }));
224    ///
225    /// panic!("Normal panic");
226    /// ```
227    #[must_use]
228    #[stable(feature = "panic_hooks", since = "1.10.0")]
229    #[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")]
230    pub const fn file(&self) -> &'a str {
231        // SAFETY: The filename is valid.
232        unsafe { self.filename.as_ref() }
233    }
234
235    /// Returns the name of the source file as a nul-terminated `CStr`.
236    ///
237    /// This is useful for interop with APIs that expect C/C++ `__FILE__` or
238    /// `std::source_location::file_name`, both of which return a nul-terminated `const char*`.
239    #[must_use]
240    #[inline]
241    #[stable(feature = "file_with_nul", since = "1.92.0")]
242    #[rustc_const_stable(feature = "file_with_nul", since = "1.92.0")]
243    #[cfg(not(feature = "ferrocene_subset"))]
244    pub const fn file_as_c_str(&self) -> &'a CStr {
245        let filename = self.filename.as_ptr();
246
247        // SAFETY: The filename is valid for `filename_len+1` bytes, so this addition can't
248        // overflow.
249        let cstr_len = unsafe { crate::mem::size_of_val_raw(filename).unchecked_add(1) };
250
251        // SAFETY: The filename is valid for `filename_len+1` bytes.
252        let slice = unsafe { crate::slice::from_raw_parts(filename.cast(), cstr_len) };
253
254        // SAFETY: The filename is guaranteed to have a trailing nul byte and no interior nul bytes.
255        unsafe { CStr::from_bytes_with_nul_unchecked(slice) }
256    }
257
258    /// Returns the line number from which the panic originated.
259    ///
260    /// # Examples
261    ///
262    /// ```should_panic
263    /// use std::panic;
264    ///
265    /// panic::set_hook(Box::new(|panic_info| {
266    ///     if let Some(location) = panic_info.location() {
267    ///         println!("panic occurred at line {}", location.line());
268    ///     } else {
269    ///         println!("panic occurred but can't get location information...");
270    ///     }
271    /// }));
272    ///
273    /// panic!("Normal panic");
274    /// ```
275    #[must_use]
276    #[stable(feature = "panic_hooks", since = "1.10.0")]
277    #[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")]
278    #[inline]
279    pub const fn line(&self) -> u32 {
280        self.line
281    }
282
283    /// Returns the column from which the panic originated.
284    ///
285    /// # Examples
286    ///
287    /// ```should_panic
288    /// use std::panic;
289    ///
290    /// panic::set_hook(Box::new(|panic_info| {
291    ///     if let Some(location) = panic_info.location() {
292    ///         println!("panic occurred at column {}", location.column());
293    ///     } else {
294    ///         println!("panic occurred but can't get location information...");
295    ///     }
296    /// }));
297    ///
298    /// panic!("Normal panic");
299    /// ```
300    #[must_use]
301    #[stable(feature = "panic_col", since = "1.25.0")]
302    #[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")]
303    #[inline]
304    pub const fn column(&self) -> u32 {
305        self.col
306    }
307}
308
309#[stable(feature = "panic_hook_display", since = "1.26.0")]
310#[cfg(not(feature = "ferrocene_subset"))]
311impl fmt::Display for Location<'_> {
312    #[inline]
313    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
314        write!(formatter, "{}:{}:{}", self.file(), self.line, self.col)
315    }
316}
317
318#[stable(feature = "panic_hooks", since = "1.10.0")]
319unsafe impl Send for Location<'_> {}
320#[stable(feature = "panic_hooks", since = "1.10.0")]
321unsafe impl Sync for Location<'_> {}