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}