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<'_> {}