core/panic/
location.rs

1use crate::ffi::CStr;
2use crate::fmt;
3
4/// A struct containing information about the location of a panic.
5///
6/// This structure is created by [`PanicHookInfo::location()`] and [`PanicInfo::location()`].
7///
8/// [`PanicInfo::location()`]: crate::panic::PanicInfo::location
9/// [`PanicHookInfo::location()`]: ../../std/panic/struct.PanicHookInfo.html#method.location
10///
11/// # Examples
12///
13/// ```should_panic
14/// use std::panic;
15///
16/// panic::set_hook(Box::new(|panic_info| {
17///     if let Some(location) = panic_info.location() {
18///         println!("panic occurred in file '{}' at line {}", location.file(), location.line());
19///     } else {
20///         println!("panic occurred but can't get location information...");
21///     }
22/// }));
23///
24/// panic!("Normal panic");
25/// ```
26///
27/// # Comparisons
28///
29/// Comparisons for equality and ordering are made in file, line, then column priority.
30/// Files are compared as strings, not `Path`, which could be unexpected.
31/// See [`Location::file`]'s documentation for more discussion.
32#[lang = "panic_location"]
33#[derive(Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
34#[stable(feature = "panic_hooks", since = "1.10.0")]
35pub struct Location<'a> {
36    // Note: this filename will have exactly one nul byte at its end, but otherwise
37    // it must never contain interior nul bytes. This is relied on for the conversion
38    // to `CStr` below.
39    //
40    // The prefix of the string without the trailing nul byte will be a regular UTF8 `str`.
41    file_bytes_with_nul: &'a [u8],
42    line: u32,
43    col: u32,
44}
45
46#[stable(feature = "panic_hooks", since = "1.10.0")]
47impl fmt::Debug for Location<'_> {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        f.debug_struct("Location")
50            .field("file", &self.file())
51            .field("line", &self.line)
52            .field("column", &self.col)
53            .finish()
54    }
55}
56
57impl<'a> Location<'a> {
58    /// Returns the source location of the caller of this function. If that function's caller is
59    /// annotated then its call location will be returned, and so on up the stack to the first call
60    /// within a non-tracked function body.
61    ///
62    /// # Examples
63    ///
64    /// ```standalone_crate
65    /// use std::panic::Location;
66    ///
67    /// /// Returns the [`Location`] at which it is called.
68    /// #[track_caller]
69    /// fn get_caller_location() -> &'static Location<'static> {
70    ///     Location::caller()
71    /// }
72    ///
73    /// /// Returns a [`Location`] from within this function's definition.
74    /// fn get_just_one_location() -> &'static Location<'static> {
75    ///     get_caller_location()
76    /// }
77    ///
78    /// let fixed_location = get_just_one_location();
79    /// assert_eq!(fixed_location.file(), file!());
80    /// assert_eq!(fixed_location.line(), 14);
81    /// assert_eq!(fixed_location.column(), 5);
82    ///
83    /// // running the same untracked function in a different location gives us the same result
84    /// let second_fixed_location = get_just_one_location();
85    /// assert_eq!(fixed_location.file(), second_fixed_location.file());
86    /// assert_eq!(fixed_location.line(), second_fixed_location.line());
87    /// assert_eq!(fixed_location.column(), second_fixed_location.column());
88    ///
89    /// let this_location = get_caller_location();
90    /// assert_eq!(this_location.file(), file!());
91    /// assert_eq!(this_location.line(), 28);
92    /// assert_eq!(this_location.column(), 21);
93    ///
94    /// // running the tracked function in a different location produces a different value
95    /// let another_location = get_caller_location();
96    /// assert_eq!(this_location.file(), another_location.file());
97    /// assert_ne!(this_location.line(), another_location.line());
98    /// assert_ne!(this_location.column(), another_location.column());
99    /// ```
100    #[must_use]
101    #[stable(feature = "track_caller", since = "1.46.0")]
102    #[rustc_const_stable(feature = "const_caller_location", since = "1.79.0")]
103    #[track_caller]
104    #[inline]
105    pub const fn caller() -> &'static Location<'static> {
106        crate::intrinsics::caller_location()
107    }
108
109    /// Returns the name of the source file from which the panic originated.
110    ///
111    /// # `&str`, not `&Path`
112    ///
113    /// The returned name refers to a source path on the compiling system, but it isn't valid to
114    /// represent this directly as a `&Path`. The compiled code may run on a different system with
115    /// a different `Path` implementation than the system providing the contents and this library
116    /// does not currently have a different "host path" type.
117    ///
118    /// The most surprising behavior occurs when "the same" file is reachable via multiple paths in
119    /// the module system (usually using the `#[path = "..."]` attribute or similar), which can
120    /// cause what appears to be identical code to return differing values from this function.
121    ///
122    /// # Cross-compilation
123    ///
124    /// This value is not suitable for passing to `Path::new` or similar constructors when the host
125    /// platform and target platform differ.
126    ///
127    /// # Examples
128    ///
129    /// ```should_panic
130    /// use std::panic;
131    ///
132    /// panic::set_hook(Box::new(|panic_info| {
133    ///     if let Some(location) = panic_info.location() {
134    ///         println!("panic occurred in file '{}'", location.file());
135    ///     } else {
136    ///         println!("panic occurred but can't get location information...");
137    ///     }
138    /// }));
139    ///
140    /// panic!("Normal panic");
141    /// ```
142    #[must_use]
143    #[stable(feature = "panic_hooks", since = "1.10.0")]
144    #[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")]
145    pub const fn file(&self) -> &str {
146        let str_len = self.file_bytes_with_nul.len() - 1;
147        // SAFETY: `file_bytes_with_nul` without the trailing nul byte is guaranteed to be
148        // valid UTF8.
149        unsafe { crate::str::from_raw_parts(self.file_bytes_with_nul.as_ptr(), str_len) }
150    }
151
152    /// Returns the name of the source file as a nul-terminated `CStr`.
153    ///
154    /// This is useful for interop with APIs that expect C/C++ `__FILE__` or
155    /// `std::source_location::file_name`, both of which return a nul-terminated `const char*`.
156    #[must_use]
157    #[unstable(feature = "file_with_nul", issue = "141727")]
158    #[inline]
159    pub const fn file_with_nul(&self) -> &CStr {
160        // SAFETY: `file_bytes_with_nul` is guaranteed to have a trailing nul byte and no
161        // interior nul bytes.
162        unsafe { CStr::from_bytes_with_nul_unchecked(self.file_bytes_with_nul) }
163    }
164
165    /// Returns the line number from which the panic originated.
166    ///
167    /// # Examples
168    ///
169    /// ```should_panic
170    /// use std::panic;
171    ///
172    /// panic::set_hook(Box::new(|panic_info| {
173    ///     if let Some(location) = panic_info.location() {
174    ///         println!("panic occurred at line {}", location.line());
175    ///     } else {
176    ///         println!("panic occurred but can't get location information...");
177    ///     }
178    /// }));
179    ///
180    /// panic!("Normal panic");
181    /// ```
182    #[must_use]
183    #[stable(feature = "panic_hooks", since = "1.10.0")]
184    #[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")]
185    #[inline]
186    pub const fn line(&self) -> u32 {
187        self.line
188    }
189
190    /// Returns the column from which the panic originated.
191    ///
192    /// # Examples
193    ///
194    /// ```should_panic
195    /// use std::panic;
196    ///
197    /// panic::set_hook(Box::new(|panic_info| {
198    ///     if let Some(location) = panic_info.location() {
199    ///         println!("panic occurred at column {}", location.column());
200    ///     } else {
201    ///         println!("panic occurred but can't get location information...");
202    ///     }
203    /// }));
204    ///
205    /// panic!("Normal panic");
206    /// ```
207    #[must_use]
208    #[stable(feature = "panic_col", since = "1.25.0")]
209    #[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")]
210    #[inline]
211    pub const fn column(&self) -> u32 {
212        self.col
213    }
214}
215
216#[stable(feature = "panic_hook_display", since = "1.26.0")]
217impl fmt::Display for Location<'_> {
218    #[inline]
219    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
220        write!(formatter, "{}:{}:{}", self.file(), self.line, self.col)
221    }
222}