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, Debug, 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
46impl<'a> Location<'a> {
47    /// Returns the source location of the caller of this function. If that function's caller is
48    /// annotated then its call location will be returned, and so on up the stack to the first call
49    /// within a non-tracked function body.
50    ///
51    /// # Examples
52    ///
53    /// ```standalone_crate
54    /// use std::panic::Location;
55    ///
56    /// /// Returns the [`Location`] at which it is called.
57    /// #[track_caller]
58    /// fn get_caller_location() -> &'static Location<'static> {
59    ///     Location::caller()
60    /// }
61    ///
62    /// /// Returns a [`Location`] from within this function's definition.
63    /// fn get_just_one_location() -> &'static Location<'static> {
64    ///     get_caller_location()
65    /// }
66    ///
67    /// let fixed_location = get_just_one_location();
68    /// assert_eq!(fixed_location.file(), file!());
69    /// assert_eq!(fixed_location.line(), 14);
70    /// assert_eq!(fixed_location.column(), 5);
71    ///
72    /// // running the same untracked function in a different location gives us the same result
73    /// let second_fixed_location = get_just_one_location();
74    /// assert_eq!(fixed_location.file(), second_fixed_location.file());
75    /// assert_eq!(fixed_location.line(), second_fixed_location.line());
76    /// assert_eq!(fixed_location.column(), second_fixed_location.column());
77    ///
78    /// let this_location = get_caller_location();
79    /// assert_eq!(this_location.file(), file!());
80    /// assert_eq!(this_location.line(), 28);
81    /// assert_eq!(this_location.column(), 21);
82    ///
83    /// // running the tracked function in a different location produces a different value
84    /// let another_location = get_caller_location();
85    /// assert_eq!(this_location.file(), another_location.file());
86    /// assert_ne!(this_location.line(), another_location.line());
87    /// assert_ne!(this_location.column(), another_location.column());
88    /// ```
89    #[must_use]
90    #[stable(feature = "track_caller", since = "1.46.0")]
91    #[rustc_const_stable(feature = "const_caller_location", since = "1.79.0")]
92    #[track_caller]
93    #[inline]
94    pub const fn caller() -> &'static Location<'static> {
95        crate::intrinsics::caller_location()
96    }
97
98    /// Returns the name of the source file from which the panic originated.
99    ///
100    /// # `&str`, not `&Path`
101    ///
102    /// The returned name refers to a source path on the compiling system, but it isn't valid to
103    /// represent this directly as a `&Path`. The compiled code may run on a different system with
104    /// a different `Path` implementation than the system providing the contents and this library
105    /// does not currently have a different "host path" type.
106    ///
107    /// The most surprising behavior occurs when "the same" file is reachable via multiple paths in
108    /// the module system (usually using the `#[path = "..."]` attribute or similar), which can
109    /// cause what appears to be identical code to return differing values from this function.
110    ///
111    /// # Cross-compilation
112    ///
113    /// This value is not suitable for passing to `Path::new` or similar constructors when the host
114    /// platform and target platform differ.
115    ///
116    /// # Examples
117    ///
118    /// ```should_panic
119    /// use std::panic;
120    ///
121    /// panic::set_hook(Box::new(|panic_info| {
122    ///     if let Some(location) = panic_info.location() {
123    ///         println!("panic occurred in file '{}'", location.file());
124    ///     } else {
125    ///         println!("panic occurred but can't get location information...");
126    ///     }
127    /// }));
128    ///
129    /// panic!("Normal panic");
130    /// ```
131    #[must_use]
132    #[stable(feature = "panic_hooks", since = "1.10.0")]
133    #[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")]
134    pub const fn file(&self) -> &str {
135        let str_len = self.file_bytes_with_nul.len() - 1;
136        // SAFETY: `file_bytes_with_nul` without the trailing nul byte is guaranteed to be
137        // valid UTF8.
138        unsafe { crate::str::from_raw_parts(self.file_bytes_with_nul.as_ptr(), str_len) }
139    }
140
141    /// Returns the name of the source file as a nul-terminated `CStr`.
142    ///
143    /// This is useful for interop with APIs that expect C/C++ `__FILE__` or
144    /// `std::source_location::file_name`, both of which return a nul-terminated `const char*`.
145    #[must_use]
146    #[unstable(feature = "file_with_nul", issue = "141727")]
147    #[inline]
148    pub const fn file_with_nul(&self) -> &CStr {
149        // SAFETY: `file_bytes_with_nul` is guaranteed to have a trailing nul byte and no
150        // interior nul bytes.
151        unsafe { CStr::from_bytes_with_nul_unchecked(self.file_bytes_with_nul) }
152    }
153
154    /// Returns the line number from which the panic originated.
155    ///
156    /// # Examples
157    ///
158    /// ```should_panic
159    /// use std::panic;
160    ///
161    /// panic::set_hook(Box::new(|panic_info| {
162    ///     if let Some(location) = panic_info.location() {
163    ///         println!("panic occurred at line {}", location.line());
164    ///     } else {
165    ///         println!("panic occurred but can't get location information...");
166    ///     }
167    /// }));
168    ///
169    /// panic!("Normal panic");
170    /// ```
171    #[must_use]
172    #[stable(feature = "panic_hooks", since = "1.10.0")]
173    #[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")]
174    #[inline]
175    pub const fn line(&self) -> u32 {
176        self.line
177    }
178
179    /// Returns the column from which the panic originated.
180    ///
181    /// # Examples
182    ///
183    /// ```should_panic
184    /// use std::panic;
185    ///
186    /// panic::set_hook(Box::new(|panic_info| {
187    ///     if let Some(location) = panic_info.location() {
188    ///         println!("panic occurred at column {}", location.column());
189    ///     } else {
190    ///         println!("panic occurred but can't get location information...");
191    ///     }
192    /// }));
193    ///
194    /// panic!("Normal panic");
195    /// ```
196    #[must_use]
197    #[stable(feature = "panic_col", since = "1.25.0")]
198    #[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")]
199    #[inline]
200    pub const fn column(&self) -> u32 {
201        self.col
202    }
203}
204
205#[stable(feature = "panic_hook_display", since = "1.26.0")]
206impl fmt::Display for Location<'_> {
207    #[inline]
208    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
209        write!(formatter, "{}:{}:{}", self.file(), self.line, self.col)
210    }
211}