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