New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Implicit caller location (third try to the unwrap/expect line info problem) #2091

Merged
merged 12 commits into from Jan 27, 2018

Conversation

@kennytm
Member

kennytm commented Jul 31, 2017

Current (Implicit caller location)

Summary

Enable accurate caller location reporting during panic in {Option, Result}::{unwrap, expect} with
the following changes:

  1. Support the #[blame_caller] function attribute, which guarantees a function has access to the
    caller information.
  2. Add an intrinsic function caller_location() (safe wrapper: Location::caller()) to retrieve
    the caller's source location.

Legacy (Semantic inlining):

@jethrogb

This comment has been minimized.

Show comment
Hide comment
@jethrogb

jethrogb Jul 31, 2017

Contributor

We could change the meaning of file!(), line!() and column!() so they are only converted to real constants after semantic-inlining (a MIR pass) instead of early during macro expansion (an AST pass). Inside #[inline(semantic)] functions, these macros behave as this RFC's core::caller::{FILE, LINE, COLUMN}. This way, we don't need to introduce panic_at_source_location!(). The drawback is losing explicit control of whether we want to report the actual location or the caller's location.

I think the macros should always resolve to the special lang item, and the lang items should also work in non-inlined functions (but you'd get the location of the lang item itself, not the caller). And maybe the lang items could be annotated such that they can be used from within the macros but not written manually.

Contributor

jethrogb commented Jul 31, 2017

We could change the meaning of file!(), line!() and column!() so they are only converted to real constants after semantic-inlining (a MIR pass) instead of early during macro expansion (an AST pass). Inside #[inline(semantic)] functions, these macros behave as this RFC's core::caller::{FILE, LINE, COLUMN}. This way, we don't need to introduce panic_at_source_location!(). The drawback is losing explicit control of whether we want to report the actual location or the caller's location.

I think the macros should always resolve to the special lang item, and the lang items should also work in non-inlined functions (but you'd get the location of the lang item itself, not the caller). And maybe the lang items could be annotated such that they can be used from within the macros but not written manually.

@kennytm

This comment has been minimized.

Show comment
Hide comment
@kennytm

kennytm Jul 31, 2017

Member

Hmm. Turning file!(), line!() and column!() just into special consts will break backward-compatibility. They need to expand into special literals (or concat! needs to pretend those lang items are literals).

const FILE: &str = file!();
const LINE: u32 = line!();
macro_rules! my_file { () => (FILE) }
macro_rules! my_line { () => (LINE) }
fn main() {
    println!("{}", concat!(file!(), ":", line!()));
    //^ Fine, prints src/main.rs:6
    println!("{}", concat!(my_file!(), ":", my_line!()));
    //~^ ERROR: expected a literal
}
Member

kennytm commented Jul 31, 2017

Hmm. Turning file!(), line!() and column!() just into special consts will break backward-compatibility. They need to expand into special literals (or concat! needs to pretend those lang items are literals).

const FILE: &str = file!();
const LINE: u32 = line!();
macro_rules! my_file { () => (FILE) }
macro_rules! my_line { () => (LINE) }
fn main() {
    println!("{}", concat!(file!(), ":", line!()));
    //^ Fine, prints src/main.rs:6
    println!("{}", concat!(my_file!(), ":", my_line!()));
    //~^ ERROR: expected a literal
}
@jethrogb

This comment has been minimized.

Show comment
Hide comment
@jethrogb

jethrogb Jul 31, 2017

Contributor

Uh oh. That might be unintended. At least the docs say it expands to an expression of type &'static str, not a literal. But I suppose we can't change that now.

Contributor

jethrogb commented Jul 31, 2017

Uh oh. That might be unintended. At least the docs say it expands to an expression of type &'static str, not a literal. But I suppose we can't change that now.

@kennytm

This comment has been minimized.

Show comment
Hide comment
@kennytm

kennytm Jul 31, 2017

Member

@jethrogb Actually expanding to a literal would be far easier than a special const item. We just add a new LitKind and make file!() and friends (which are procedural macros) the only way to generate such LitKind.

However, doing so would make panic not able to use the static _FILE_LINE_COL = (file!(), line!(), column!()); code-size optimization trick.

Member

kennytm commented Jul 31, 2017

@jethrogb Actually expanding to a literal would be far easier than a special const item. We just add a new LitKind and make file!() and friends (which are procedural macros) the only way to generate such LitKind.

However, doing so would make panic not able to use the static _FILE_LINE_COL = (file!(), line!(), column!()); code-size optimization trick.

@eddyb

This comment has been minimized.

Show comment
Hide comment
@eddyb

eddyb Jul 31, 2017

Member

Is that optimization worth it? It only means the value is not duplicated between crates.
&(file!(), line!()) hasn't had the values on the stack since before 1.0 (rvalue promotion to 'static - you still need a feature gate to get the borrowck to believe it but the rest of the compiler does it anyway as an optimization) - and we deduplicate constants like that within a crate already.

Member

eddyb commented Jul 31, 2017

Is that optimization worth it? It only means the value is not duplicated between crates.
&(file!(), line!()) hasn't had the values on the stack since before 1.0 (rvalue promotion to 'static - you still need a feature gate to get the borrowck to believe it but the rest of the compiler does it anyway as an optimization) - and we deduplicate constants like that within a crate already.

@steveklabnik

This comment has been minimized.

Show comment
Hide comment
@steveklabnik

steveklabnik Aug 1, 2017

Member

I just want to say that this is one of the most well-written RFCs I've ever seen. Amazing job @kennytm !

Member

steveklabnik commented Aug 1, 2017

I just want to say that this is one of the most well-written RFCs I've ever seen. Amazing job @kennytm !

Show outdated Hide outdated text/0000-inline-semantic.md
Show outdated Hide outdated text/0000-inline-semantic.md
Show outdated Hide outdated text/0000-inline-semantic.md
@est31

This comment has been minimized.

Show comment
Hide comment
@est31

est31 Aug 2, 2017

Contributor

Thanks @kennytm for writing such a well written RFC! It is definitely important to fix this issue of the unwrap function.

First on a matter of coordination. @japaric has opened RFC #2070 recently which concerns enabling panic! to work on no-std applications without using unstable features. As the two RFCs both affect the panic mechanism of Rust, there is some level of overlap here, it would be great if you both could coordinate to make sure the RFCs are compatible with each other.

Semantic inlining vs traditional inlining

I want to emphasize what @sfackler has pointed out above: #[inline(semantic)] is different from traditional inlining. It violates the usual boundary between the calling and the called function to a greater extent than traditional inlining. With traditional inlining, you usually want to retain the separation between the caller and the calee in the stack trace and get annoyed if the compiler/debugger mixes them up, and with #[inline(semantic)] you want the exact opposite. Teaching people that this is the same mechanism will only cause confusion IMO.

Due to the point above, the #[inline(semantic)] name is much greater than the alternatives mentioned. I especially like how "semantic" conveys the meaning that there is an actual "flow of data" from the caller to the callee (the location info), independent from parameters passed. I wouldn't like a rename to #[inline(mir)] because right now, there is no mention of MIR in the language anywhere, and I'd say its just an implementation detail, which it should IMO stay. #[inline(force)] sounds too similar to #[inline(always)], while both are semantically different things.

If the #[inline(semantic)] name can be improved, then by making it less close to the traditional inlining mechanism.

location info lang items

As the file!() macro and friends can't expand to simple literals any more, the RFC proposes the addition of lang items that get classified as constants, but get special meaning as e.g. constant folding must ignore them. They essentially get treated like dummies until the MIR inlining pass. The basic problem here is that you need to have some dummy that the MIR semantic inlining pass can expand to a literal, but which can also be expressed by the token stream API that macros use as input and output. The RFC proposes to solve this via addition of lang items, which is a great way of doing this.

lang items get treated specially by the compiler, so they are always more than just what you call them. You still need to find some category in the existing language where the a feature fits in really well, which you then use to expose it to the remainder of the language. I think the category chosen right now, constants, is not really suiting. For me, an important property of constants is that they are a never changing value, everywhere the same. There are mutable statics, but this is a different thing obviously. You'll have to add an exception to constant folding to specifically avoid them, and you need to define dummy values at the declaration site that might be mistaken by the reader for actual values. And, you have to introduce a new concept of "magic" constants to explain them.

A much greater way of categorizing the lang items would be intrinsic functions marked with #[inline(semantic)]. Intrinsic because they will be implemented in the compiler, and #[inline(semantic)] because the the context of the calling place will affect their evaluation (Of course, it is natural that a call to an intrinsic function gets inlined, but marking it #[inline(semantic)] gives us that additional meaning). Even if the intrinsics that exist in the compiler right now all get converted to llvm intrinsics (so adding an intrinsic that doesn't reach LLVM would be a novelty), the lang items fit much better into the class of intrinsics than into the class of constants.

Forwards compatibility

When I added column info to panic printing, I was pleasantly surprised that panic info printing was forwards compatible to a great degree; I didn't have to avoid breaking any stable API. This property should be preserved by the RFC IMO.

Possible future changes include e.g. include more information from the span, like e.g. the ending line and colum, or crate names, or similar. Of course you can argue how useful these additions are, but I think regardless from one or another being not very useful, keeping forwards compat is important!

The panic hook mechanism has the opaque Location struct which is very forward compatible. If we add a caller_location macro, it should return this struct instead of a tuple.

I haven't found any other places where this RFC is incompatible with future extensions, but if there are any, it would be great to get rid of them.

Contributor

est31 commented Aug 2, 2017

Thanks @kennytm for writing such a well written RFC! It is definitely important to fix this issue of the unwrap function.

First on a matter of coordination. @japaric has opened RFC #2070 recently which concerns enabling panic! to work on no-std applications without using unstable features. As the two RFCs both affect the panic mechanism of Rust, there is some level of overlap here, it would be great if you both could coordinate to make sure the RFCs are compatible with each other.

Semantic inlining vs traditional inlining

I want to emphasize what @sfackler has pointed out above: #[inline(semantic)] is different from traditional inlining. It violates the usual boundary between the calling and the called function to a greater extent than traditional inlining. With traditional inlining, you usually want to retain the separation between the caller and the calee in the stack trace and get annoyed if the compiler/debugger mixes them up, and with #[inline(semantic)] you want the exact opposite. Teaching people that this is the same mechanism will only cause confusion IMO.

Due to the point above, the #[inline(semantic)] name is much greater than the alternatives mentioned. I especially like how "semantic" conveys the meaning that there is an actual "flow of data" from the caller to the callee (the location info), independent from parameters passed. I wouldn't like a rename to #[inline(mir)] because right now, there is no mention of MIR in the language anywhere, and I'd say its just an implementation detail, which it should IMO stay. #[inline(force)] sounds too similar to #[inline(always)], while both are semantically different things.

If the #[inline(semantic)] name can be improved, then by making it less close to the traditional inlining mechanism.

location info lang items

As the file!() macro and friends can't expand to simple literals any more, the RFC proposes the addition of lang items that get classified as constants, but get special meaning as e.g. constant folding must ignore them. They essentially get treated like dummies until the MIR inlining pass. The basic problem here is that you need to have some dummy that the MIR semantic inlining pass can expand to a literal, but which can also be expressed by the token stream API that macros use as input and output. The RFC proposes to solve this via addition of lang items, which is a great way of doing this.

lang items get treated specially by the compiler, so they are always more than just what you call them. You still need to find some category in the existing language where the a feature fits in really well, which you then use to expose it to the remainder of the language. I think the category chosen right now, constants, is not really suiting. For me, an important property of constants is that they are a never changing value, everywhere the same. There are mutable statics, but this is a different thing obviously. You'll have to add an exception to constant folding to specifically avoid them, and you need to define dummy values at the declaration site that might be mistaken by the reader for actual values. And, you have to introduce a new concept of "magic" constants to explain them.

A much greater way of categorizing the lang items would be intrinsic functions marked with #[inline(semantic)]. Intrinsic because they will be implemented in the compiler, and #[inline(semantic)] because the the context of the calling place will affect their evaluation (Of course, it is natural that a call to an intrinsic function gets inlined, but marking it #[inline(semantic)] gives us that additional meaning). Even if the intrinsics that exist in the compiler right now all get converted to llvm intrinsics (so adding an intrinsic that doesn't reach LLVM would be a novelty), the lang items fit much better into the class of intrinsics than into the class of constants.

Forwards compatibility

When I added column info to panic printing, I was pleasantly surprised that panic info printing was forwards compatible to a great degree; I didn't have to avoid breaking any stable API. This property should be preserved by the RFC IMO.

Possible future changes include e.g. include more information from the span, like e.g. the ending line and colum, or crate names, or similar. Of course you can argue how useful these additions are, but I think regardless from one or another being not very useful, keeping forwards compat is important!

The panic hook mechanism has the opaque Location struct which is very forward compatible. If we add a caller_location macro, it should return this struct instead of a tuple.

I haven't found any other places where this RFC is incompatible with future extensions, but if there are any, it would be great to get rid of them.

@est31 est31 referenced this pull request Aug 2, 2017

Merged

Improvements to `proc_macro::Span` API #43604

2 of 2 tasks complete
@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Aug 2, 2017

Contributor

I certainly agree this is annoying problem, and one worth solving. It seems to me that the big decision to be made is indeed whether to assume that debuginfo is not a viable path -- I believe that other languages have solved this by having the ability to annotate functions as being "implementation details", and hence these functions get omitted or overlooked in a backtrace (i.e., by the backtrace machinery). But I can't find any citations for this second approach.

In a way, the two are similar. The idea is to label functions that users probably don't want to see. I think one could argue that implementing this via inlining is an 'implementation detail', right? The important bit is that the various new macros in question will work give the position info from the caller, right? (Or are there other things I am overlooking where it is crucial that the function is inlined at the MIR level?)

Contributor

nikomatsakis commented Aug 2, 2017

I certainly agree this is annoying problem, and one worth solving. It seems to me that the big decision to be made is indeed whether to assume that debuginfo is not a viable path -- I believe that other languages have solved this by having the ability to annotate functions as being "implementation details", and hence these functions get omitted or overlooked in a backtrace (i.e., by the backtrace machinery). But I can't find any citations for this second approach.

In a way, the two are similar. The idea is to label functions that users probably don't want to see. I think one could argue that implementing this via inlining is an 'implementation detail', right? The important bit is that the various new macros in question will work give the position info from the caller, right? (Or are there other things I am overlooking where it is crucial that the function is inlined at the MIR level?)

@est31

This comment has been minimized.

Show comment
Hide comment
@est31

est31 Aug 2, 2017

Contributor

Or are there other things I am overlooking where it is crucial that the function is inlined at the MIR level

It is important that the function is inlined in MIR instead of waiting for LLVM because the location info is separate for each time a function gets inlined. We want to expand the location info to a literal at compile time and not read it from debuginfo at runtime so that this works with builds whose debuginfo is stripped. IMO we should still offer an option to strip even this info (I think right now its possible to do that via custom panic_fmt lang items, not sure though), but there should be a mode where main debuginfo is being stripped, but the panic location is still being printed.

Contributor

est31 commented Aug 2, 2017

Or are there other things I am overlooking where it is crucial that the function is inlined at the MIR level

It is important that the function is inlined in MIR instead of waiting for LLVM because the location info is separate for each time a function gets inlined. We want to expand the location info to a literal at compile time and not read it from debuginfo at runtime so that this works with builds whose debuginfo is stripped. IMO we should still offer an option to strip even this info (I think right now its possible to do that via custom panic_fmt lang items, not sure though), but there should be a mode where main debuginfo is being stripped, but the panic location is still being printed.

@main--

This comment has been minimized.

Show comment
Hide comment
@main--

main-- Aug 2, 2017

Inlining is so painful to debug, I'd like to avoid it here if at all possible.

I also don't really agree with your rationale that relying on debug info is not an option. In my experience, the location information from a panic message is rarely useful (at all) in trying to figure out what went wrong - you need at least a backtrace for that.

I just don't see the value in trying to provide this information even when there's no debug info. It's certainly of no immediate use to end users, but I can imagine it being relevant for bug reports. However as I said, a bug report without a backtrace is rarely useful anyways.

From this perspective, I would suggest a very different approach: Rely on debug info entirely. Then, the solution to this problem can be as simple as just always printing more than a single stack frame (say 3) by default (having to re-run from the beginning with RUST_BACKTRACE=1 is always very annoying).

Release builds are of course an issue but I believe there's a much better solution for that: Build with debug symbols but save them externally, in their own file. This also means that bug reports from end users are now much more useful overall as the backtrace can be decoded using the symbol file (even core dumps should work, right?).

Anyways, that's just what I'd like to see from my personal experience.

main-- commented Aug 2, 2017

Inlining is so painful to debug, I'd like to avoid it here if at all possible.

I also don't really agree with your rationale that relying on debug info is not an option. In my experience, the location information from a panic message is rarely useful (at all) in trying to figure out what went wrong - you need at least a backtrace for that.

I just don't see the value in trying to provide this information even when there's no debug info. It's certainly of no immediate use to end users, but I can imagine it being relevant for bug reports. However as I said, a bug report without a backtrace is rarely useful anyways.

From this perspective, I would suggest a very different approach: Rely on debug info entirely. Then, the solution to this problem can be as simple as just always printing more than a single stack frame (say 3) by default (having to re-run from the beginning with RUST_BACKTRACE=1 is always very annoying).

Release builds are of course an issue but I believe there's a much better solution for that: Build with debug symbols but save them externally, in their own file. This also means that bug reports from end users are now much more useful overall as the backtrace can be decoded using the symbol file (even core dumps should work, right?).

Anyways, that's just what I'd like to see from my personal experience.

@petrochenkov

This comment has been minimized.

Show comment
Hide comment
@petrochenkov

petrochenkov Aug 2, 2017

Contributor

@est31

It is important that the function is inlined in MIR instead of waiting for LLVM because the location info is separate for each time a function gets inlined.

Can inlining be replaced with a special ABI with modified calling conventions using additional implicit argument for passing (pointer to) the location?
When such functions are converted into function pointers some shims will have to be created though.

Contributor

petrochenkov commented Aug 2, 2017

@est31

It is important that the function is inlined in MIR instead of waiting for LLVM because the location info is separate for each time a function gets inlined.

Can inlining be replaced with a special ABI with modified calling conventions using additional implicit argument for passing (pointer to) the location?
When such functions are converted into function pointers some shims will have to be created though.

@repax

This comment has been minimized.

Show comment
Hide comment
@repax

repax Aug 2, 2017

I like this idea of MIR inlining a lot. As for another use case: It would be nice if the callee could be able to verify, at compile-time, that the caller is eligible, given some policy or otherwise.

I'd also like to see an implementation of this panicking business where this source location data could be stripped from the binary. Points of possible panic are rather prevalent.

repax commented Aug 2, 2017

I like this idea of MIR inlining a lot. As for another use case: It would be nice if the callee could be able to verify, at compile-time, that the caller is eligible, given some policy or otherwise.

I'd also like to see an implementation of this panicking business where this source location data could be stripped from the binary. Points of possible panic are rather prevalent.

@petrochenkov

This comment has been minimized.

Show comment
Hide comment
@petrochenkov

petrochenkov Aug 2, 2017

Contributor

@eddyb @kennytm
Could you elaborate why the ABI tweak is a bad alternative compared to inlining?

Contributor

petrochenkov commented Aug 2, 2017

@eddyb @kennytm
Could you elaborate why the ABI tweak is a bad alternative compared to inlining?

@eddyb

This comment has been minimized.

Show comment
Hide comment
@eddyb

eddyb Aug 2, 2017

Member

@petrochenkov It's not as bad as it is involving in the backend. Unless you mean it's all in the Rust compiler, which could make it a MIR pass, which, uhh, would be vaguely equivalent to inlining.
If it's not a new calling convention (see: Swift) but just an extra argument rustc adds, that's fine.

Member

eddyb commented Aug 2, 2017

@petrochenkov It's not as bad as it is involving in the backend. Unless you mean it's all in the Rust compiler, which could make it a MIR pass, which, uhh, would be vaguely equivalent to inlining.
If it's not a new calling convention (see: Swift) but just an extra argument rustc adds, that's fine.

@petrochenkov

This comment has been minimized.

Show comment
Hide comment
@petrochenkov

petrochenkov Aug 2, 2017

Contributor

Yeah, I meant normal calling conventions (in low level sense), just an extra argument implicitly added during translation.

Contributor

petrochenkov commented Aug 2, 2017

Yeah, I meant normal calling conventions (in low level sense), just an extra argument implicitly added during translation.

@kennytm

This comment has been minimized.

Show comment
Hide comment
@kennytm

kennytm Aug 2, 2017

Member

Thanks for the feedback 🙂 Long post below.


@est31 on Forwards compatibility

The current PoC did not change the how panic works besides relaxing the lifetime requirement of core::panicking::panic, so there should be no problem with future changes of Location and #2070.


@sfackler @est31 on Traditional inlining vs Semantic inlining, and Location info lang item

This RFC itself did not propose changing file!() as friends (yet). The actual file!() was still retained, e.g.

#[inline(semantic)]
fn boom() {
    panic!("kaboom");  // line 3
}
fn main() {
    boom(); // line 6
}

with the current implementation, the panic will still point to line 3. So this is still somewhat like traditional inlining.

To entirely blur the line between caller/callee, we need to change file!() to expand to a special literal (which has no source representation) as mentioned in the first 5 comments. It cannot be an intrinsic or const or static because concat!(line!()) or include_str!(file!()) outside of #[inline(semantic)] should not be affected.

Assuming we don't care about panic's optimization, the only hazard is that,

#[inline(semantic)]
fn foo() -> bool {
    const LINE: u32 = line!();  // <-- this will not be the caller's line
    let l = line!() // <-- this will be the caller's line
    l == LINE // usually false
}

The static/const should trigger an error something similar to E0401. There should also be file!(after_macro_expansion_but_before_inline_semantic) to provide the traditional behavior.

I'm open to keeping core::caller::* and change those into intrinsics, but I'll also see would going through the LitKind route be too dramatic a change. I may modify the RFC after that.


@nikomatsakis @main-- on Debuginfo

Disclaimer: I've never used a debugger on rust generated program for serious debugging because (1) printf-debugging is much more accurate and easier (2) lldb doesn't seem to work properly[a] (3) the only time I've used lldb is to obtain a more correct backtrace, since RUST_BACKTRACE=1 does not work at all on macOS (rust-lang/rust#24346). So I am certainly biased here 😆.

The biggest reason debuginfo is not reliable because it is often absent. And it is absent because it is not generated by default, is huge (>100% size of the executable) and leaks sensitive source code info for propriety programs. Look, even rustc does not ship with any debuginfo

$ find ~/.rustup -name '*.dSYM'

$

💭 Maybe rustup should provide an optional "debuginfo" component 😉

There is also the issue that RUST_BACKTRACE=1 is not enable by default, partly because it is slow (rust-lang/rust#42295), and also because sometimes your system just doesn't have libunwind and you can't even get one level of backtrace.

And I just feel that a systems language with thin runtime shouldn't require a runtime solution to get the caller info.

💭 BTW I wonder why our CI system does not include RUST_BACKTRACE=1? Currently we have logs like https://travis-ci.org/rust-lang/rust/jobs/259620982 (from rust-lang/rust#43506) which fixing requires guesswork.

Note: [a]: lldb "step" jumps around randomly, "print" often don't work probably because the variable is optimized away etc. Sorry I didn't try to reproduce them properly, so no issues filed.


@nikomatsakis on Omitting backtrace (a.k.a. #1744)

Yes implementing this via inlining is an implementation detail. And yes the important bit is the various lang items will work. But I disagree that the idea is to label functions that users don't want to see. In fact, if possible I want unwrap/expect to remain visible in the stack trace, like

thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', 1.rs:7:4
stack backtrace:
  10: (garbage omitted for brevity)
  11: <core::option::Option<T>>::unwrap
  12: _1::main
  13: __rust_maybe_catch_panic
  14: std::panicking::try
  15: std::rt::lang_start
  16: main

This could be done by cloning the function's MIR + rewrite + redirect the caller, instead of inlining them. That is more aligned to @sfackler's comment that #[inline(semantic)] is parametrization by call site. But I can't find the way to add a Mir yet; a MirPass can only modify existing MIR. Anyway it is just an implementation detail.

Omitting part of backtrace inside an IDE is not a new idea, Xcode supported this very long time ago, even with dynamic granularity setting. But I'm not aware of any languages besides Swift that allow programs to annotate functions as unimportant themselves, at least not in Python or Java.

The problem isn't that the backtrace is too verbose, but it is not precise enough at the right place due to (traditional) inlining, or that the backtrace does not include the line number and you used more than one unwrap in the function.


@petrochenkov on Special ABI

It won't work because fn(T) and extern "abi" fn(T) are two different types. If they are the same type then it is no different from using an #[attribute].

Member

kennytm commented Aug 2, 2017

Thanks for the feedback 🙂 Long post below.


@est31 on Forwards compatibility

The current PoC did not change the how panic works besides relaxing the lifetime requirement of core::panicking::panic, so there should be no problem with future changes of Location and #2070.


@sfackler @est31 on Traditional inlining vs Semantic inlining, and Location info lang item

This RFC itself did not propose changing file!() as friends (yet). The actual file!() was still retained, e.g.

#[inline(semantic)]
fn boom() {
    panic!("kaboom");  // line 3
}
fn main() {
    boom(); // line 6
}

with the current implementation, the panic will still point to line 3. So this is still somewhat like traditional inlining.

To entirely blur the line between caller/callee, we need to change file!() to expand to a special literal (which has no source representation) as mentioned in the first 5 comments. It cannot be an intrinsic or const or static because concat!(line!()) or include_str!(file!()) outside of #[inline(semantic)] should not be affected.

Assuming we don't care about panic's optimization, the only hazard is that,

#[inline(semantic)]
fn foo() -> bool {
    const LINE: u32 = line!();  // <-- this will not be the caller's line
    let l = line!() // <-- this will be the caller's line
    l == LINE // usually false
}

The static/const should trigger an error something similar to E0401. There should also be file!(after_macro_expansion_but_before_inline_semantic) to provide the traditional behavior.

I'm open to keeping core::caller::* and change those into intrinsics, but I'll also see would going through the LitKind route be too dramatic a change. I may modify the RFC after that.


@nikomatsakis @main-- on Debuginfo

Disclaimer: I've never used a debugger on rust generated program for serious debugging because (1) printf-debugging is much more accurate and easier (2) lldb doesn't seem to work properly[a] (3) the only time I've used lldb is to obtain a more correct backtrace, since RUST_BACKTRACE=1 does not work at all on macOS (rust-lang/rust#24346). So I am certainly biased here 😆.

The biggest reason debuginfo is not reliable because it is often absent. And it is absent because it is not generated by default, is huge (>100% size of the executable) and leaks sensitive source code info for propriety programs. Look, even rustc does not ship with any debuginfo

$ find ~/.rustup -name '*.dSYM'

$

💭 Maybe rustup should provide an optional "debuginfo" component 😉

There is also the issue that RUST_BACKTRACE=1 is not enable by default, partly because it is slow (rust-lang/rust#42295), and also because sometimes your system just doesn't have libunwind and you can't even get one level of backtrace.

And I just feel that a systems language with thin runtime shouldn't require a runtime solution to get the caller info.

💭 BTW I wonder why our CI system does not include RUST_BACKTRACE=1? Currently we have logs like https://travis-ci.org/rust-lang/rust/jobs/259620982 (from rust-lang/rust#43506) which fixing requires guesswork.

Note: [a]: lldb "step" jumps around randomly, "print" often don't work probably because the variable is optimized away etc. Sorry I didn't try to reproduce them properly, so no issues filed.


@nikomatsakis on Omitting backtrace (a.k.a. #1744)

Yes implementing this via inlining is an implementation detail. And yes the important bit is the various lang items will work. But I disagree that the idea is to label functions that users don't want to see. In fact, if possible I want unwrap/expect to remain visible in the stack trace, like

thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', 1.rs:7:4
stack backtrace:
  10: (garbage omitted for brevity)
  11: <core::option::Option<T>>::unwrap
  12: _1::main
  13: __rust_maybe_catch_panic
  14: std::panicking::try
  15: std::rt::lang_start
  16: main

This could be done by cloning the function's MIR + rewrite + redirect the caller, instead of inlining them. That is more aligned to @sfackler's comment that #[inline(semantic)] is parametrization by call site. But I can't find the way to add a Mir yet; a MirPass can only modify existing MIR. Anyway it is just an implementation detail.

Omitting part of backtrace inside an IDE is not a new idea, Xcode supported this very long time ago, even with dynamic granularity setting. But I'm not aware of any languages besides Swift that allow programs to annotate functions as unimportant themselves, at least not in Python or Java.

The problem isn't that the backtrace is too verbose, but it is not precise enough at the right place due to (traditional) inlining, or that the backtrace does not include the line number and you used more than one unwrap in the function.


@petrochenkov on Special ABI

It won't work because fn(T) and extern "abi" fn(T) are two different types. If they are the same type then it is no different from using an #[attribute].

@main--

This comment has been minimized.

Show comment
Hide comment
@main--

main-- Aug 2, 2017

I have used gdb on Rust a few times and I agree that it could use some improvement. The seemingly random jumps you're seeing are probably due to the fact that the debug symbols emitted by rustc tend to associate almost every instruction with the original statement that caused it to be generated. This is rather problematic as optimizations shuffle things around quite a bit.

But I consider this a bug that should be fixed one day (C/C++ compilers have a decent workaround).


I'm well aware that debug info is rarely shipped in releases. I even believe storing debug info externally (like Microsoft have been doing for ages with their pdb files) is very unconventional in the Unix world today (FWIW Ubuntu do provide symbol files for some of their packages). I wonder why? Is there any big drawback I'm not aware of?

The biggest reason debuginfo is not reliable because it is often absent. And it is absent because it is not generated by default, is huge (>100% size of the executable) and leaks sensitive source code info for propriety programs. Look, even rustc does not ship with any debuginfo

Yes, this is certainly an issue today. My suggestion was not "just use debuginfo", it was "make sure debug symbols exist (throughout the ecosystem), then just use that". Most notably:

  • You would not usually ship a symbol file to end users. In the case of proprietary software with binary distribution this means that it does not leak any information about your source code (not even the the file names and location numbers for panicking).
  • You should still be able to decode a backtrace from those end users on your side using the symbol files (e.g. in a bug report).
  • And in the case of open source software, a user that wants to debug the application can just download the symbol file as well and start debugging.

💭 Maybe rustup should provide an optional "debuginfo" component 😉

👍 It should!

And again, my core argument here is basically that in a world where rustc generates symbol files for every release build by default, not having these symbols always means that showing line info is undesirable.

main-- commented Aug 2, 2017

I have used gdb on Rust a few times and I agree that it could use some improvement. The seemingly random jumps you're seeing are probably due to the fact that the debug symbols emitted by rustc tend to associate almost every instruction with the original statement that caused it to be generated. This is rather problematic as optimizations shuffle things around quite a bit.

But I consider this a bug that should be fixed one day (C/C++ compilers have a decent workaround).


I'm well aware that debug info is rarely shipped in releases. I even believe storing debug info externally (like Microsoft have been doing for ages with their pdb files) is very unconventional in the Unix world today (FWIW Ubuntu do provide symbol files for some of their packages). I wonder why? Is there any big drawback I'm not aware of?

The biggest reason debuginfo is not reliable because it is often absent. And it is absent because it is not generated by default, is huge (>100% size of the executable) and leaks sensitive source code info for propriety programs. Look, even rustc does not ship with any debuginfo

Yes, this is certainly an issue today. My suggestion was not "just use debuginfo", it was "make sure debug symbols exist (throughout the ecosystem), then just use that". Most notably:

  • You would not usually ship a symbol file to end users. In the case of proprietary software with binary distribution this means that it does not leak any information about your source code (not even the the file names and location numbers for panicking).
  • You should still be able to decode a backtrace from those end users on your side using the symbol files (e.g. in a bug report).
  • And in the case of open source software, a user that wants to debug the application can just download the symbol file as well and start debugging.

💭 Maybe rustup should provide an optional "debuginfo" component 😉

👍 It should!

And again, my core argument here is basically that in a world where rustc generates symbol files for every release build by default, not having these symbols always means that showing line info is undesirable.

@matthieu-m

This comment has been minimized.

Show comment
Hide comment
@matthieu-m

matthieu-m Aug 2, 2017

First of all, thank you for raising this issue and providing an avenue of discussion; it seems that whenever I write tests I have to have at least this one instance of Option::expect panicking and having to rerun the tests with backtracing enable to get a clue as to where the issue happened.


I think that to solve the optimization issue it could be useful to have a special flag/setting which strips column information from debug information and source locations (making them 0, or even removing them altogether).

This is something that gcc and clang have, with -glevel=1 (0: no debug, 1: minimal debug and 3: full debug). It is an interesting compromise to get some level of backtrace/source-level tracking without completely bloating the generated binary.


With regard to your proposal, I am not a fan of inlining. It seems to me inlining is limited and may cause issues:

  • does not work with recursive functions (mentioned in RFC, but should be added to Drawback section),
  • does not work with polymorphic calls,
  • poorly works with large functions.

The latter can be worked around by having a lint advising users to create a private function with the bulk of the implementation and then using the current public function as a forwarding shell (which can then be inlined without bloat). However it does mean shifting the burden onto the users.

As a result, much as did @petrochenkov, I too wonder whether a different ABI/magic parameter would not be a better solution.


As a strawman proposal, I'll go with the "magic parameter" approach:

#[ghost]
fn panic() -> ! {
    let loc = location!(); // magically retrieves the source location from the ghost argument
}

//  Exposed as parameter when taking a function to pointer, or converting to `Fn`.
static PANIC: fn(&'static ::core::source::Location) -> ! = panic as fn(&'static ::core::source::Location) -> !;

//  Can be passed as regular parameter, even though not part of "official" function signature.
panic(location!());

The essence here is that if we are to be magical, I don't see why we cannot have simili default parameters.

Calling a #[ghost] function from a #[ghost] function should either:

  • magically form a backtrace: would need an array of Location then, and unsure how it would work with polymorphic calls,
  • or simply forward the current location.

The latter is fully decidable at compile-time.

There is one question: how should specialization/implementation of a trait method work. There are two alternatives as far as I can see:

  • If the base method is #[ghost] then the specialization/implementation MUST be #[ghost] too; and vice versa.
  • The specialization/implementation being #[ghost] is independent of the base method:
    • If the base is #[ghost] and specialization/implementation is not, then the specialization/implementation ignores the magic argument (still passed for ABI reasons),
    • If the base is not #[ghost] and the specialization/implementation is, then when called statically it has the caller's location but when called dynamically it has its own location (essentially, two functions are generated).

I am not sure which is best; I would recommend starting with the first one to accumulate experience and check whether this is really an issue to start with.

matthieu-m commented Aug 2, 2017

First of all, thank you for raising this issue and providing an avenue of discussion; it seems that whenever I write tests I have to have at least this one instance of Option::expect panicking and having to rerun the tests with backtracing enable to get a clue as to where the issue happened.


I think that to solve the optimization issue it could be useful to have a special flag/setting which strips column information from debug information and source locations (making them 0, or even removing them altogether).

This is something that gcc and clang have, with -glevel=1 (0: no debug, 1: minimal debug and 3: full debug). It is an interesting compromise to get some level of backtrace/source-level tracking without completely bloating the generated binary.


With regard to your proposal, I am not a fan of inlining. It seems to me inlining is limited and may cause issues:

  • does not work with recursive functions (mentioned in RFC, but should be added to Drawback section),
  • does not work with polymorphic calls,
  • poorly works with large functions.

The latter can be worked around by having a lint advising users to create a private function with the bulk of the implementation and then using the current public function as a forwarding shell (which can then be inlined without bloat). However it does mean shifting the burden onto the users.

As a result, much as did @petrochenkov, I too wonder whether a different ABI/magic parameter would not be a better solution.


As a strawman proposal, I'll go with the "magic parameter" approach:

#[ghost]
fn panic() -> ! {
    let loc = location!(); // magically retrieves the source location from the ghost argument
}

//  Exposed as parameter when taking a function to pointer, or converting to `Fn`.
static PANIC: fn(&'static ::core::source::Location) -> ! = panic as fn(&'static ::core::source::Location) -> !;

//  Can be passed as regular parameter, even though not part of "official" function signature.
panic(location!());

The essence here is that if we are to be magical, I don't see why we cannot have simili default parameters.

Calling a #[ghost] function from a #[ghost] function should either:

  • magically form a backtrace: would need an array of Location then, and unsure how it would work with polymorphic calls,
  • or simply forward the current location.

The latter is fully decidable at compile-time.

There is one question: how should specialization/implementation of a trait method work. There are two alternatives as far as I can see:

  • If the base method is #[ghost] then the specialization/implementation MUST be #[ghost] too; and vice versa.
  • The specialization/implementation being #[ghost] is independent of the base method:
    • If the base is #[ghost] and specialization/implementation is not, then the specialization/implementation ignores the magic argument (still passed for ABI reasons),
    • If the base is not #[ghost] and the specialization/implementation is, then when called statically it has the caller's location but when called dynamically it has its own location (essentially, two functions are generated).

I am not sure which is best; I would recommend starting with the first one to accumulate experience and check whether this is really an issue to start with.

@kennytm

This comment has been minimized.

Show comment
Hide comment
@kennytm

kennytm Aug 2, 2017

Member

@matthieu-m I have thought of magic parameter and tried to implement that 3 times but it involves too many parts of compilers to make it work 😄 (AST/HIR to split a function into two, and MIR to rewrite the call, probably something more which I forgotten).

Essentially #[ghost] would generate two functions,

#[ghost]
fn x(args: T) -> R { ... }

// =>

#[ghost(redirect="def_id of x$real")]
fn x(args: T) -> R {
    x$real(args, &Location::current())
}

fn x$real(args: T, location: &Location) -> R {
   ...
}

then, after type-checking, any direct call to x would be rewritten to a call to x$real.

#[ghost] cannot use the "and vice versa" option because I want Index::index be #[ghost] too, and that would break downstream source code. Allowing panic to accept an optional parameter is probably not good for inference, since this is basically function overloading which Rust avoids.

Some questions for this:

  • How should visibility be handled (the x$real usually comes from an external crate)
  • How to store the xx$real relationship (queries?)
  • How to do add the x$real without adding an item to the trait
  • What happens when I take a function pointer. Note that the type of Option::unwrap must be coercible to fn(Option<T>) -> T, it cannot be fn(Option<T>, &Location) -> T.
  • Should x be #[inline(always)]

After thinking a bit of these, I find that the effect is very similar to inlining in terms of what I want to solve (unwrap and index), and went with that instead.

Member

kennytm commented Aug 2, 2017

@matthieu-m I have thought of magic parameter and tried to implement that 3 times but it involves too many parts of compilers to make it work 😄 (AST/HIR to split a function into two, and MIR to rewrite the call, probably something more which I forgotten).

Essentially #[ghost] would generate two functions,

#[ghost]
fn x(args: T) -> R { ... }

// =>

#[ghost(redirect="def_id of x$real")]
fn x(args: T) -> R {
    x$real(args, &Location::current())
}

fn x$real(args: T, location: &Location) -> R {
   ...
}

then, after type-checking, any direct call to x would be rewritten to a call to x$real.

#[ghost] cannot use the "and vice versa" option because I want Index::index be #[ghost] too, and that would break downstream source code. Allowing panic to accept an optional parameter is probably not good for inference, since this is basically function overloading which Rust avoids.

Some questions for this:

  • How should visibility be handled (the x$real usually comes from an external crate)
  • How to store the xx$real relationship (queries?)
  • How to do add the x$real without adding an item to the trait
  • What happens when I take a function pointer. Note that the type of Option::unwrap must be coercible to fn(Option<T>) -> T, it cannot be fn(Option<T>, &Location) -> T.
  • Should x be #[inline(always)]

After thinking a bit of these, I find that the effect is very similar to inlining in terms of what I want to solve (unwrap and index), and went with that instead.

@kennytm

This comment has been minimized.

Show comment
Hide comment
@kennytm

kennytm Aug 2, 2017

Member

@main--

I even believe storing debug info externally (like Microsoft have been doing for ages with their pdb files) is very unconventional in the Unix world today (FWIW Ubuntu do provide symbol files for some of their packages).

ELF-based systems like Linux and BSD do embed DWARF section in the binary. But macOS store them externally in a *.dSYM folder. MSVC uses *.pdb as you've mentioned.

Member

kennytm commented Aug 2, 2017

@main--

I even believe storing debug info externally (like Microsoft have been doing for ages with their pdb files) is very unconventional in the Unix world today (FWIW Ubuntu do provide symbol files for some of their packages).

ELF-based systems like Linux and BSD do embed DWARF section in the binary. But macOS store them externally in a *.dSYM folder. MSVC uses *.pdb as you've mentioned.

@main--

This comment has been minimized.

Show comment
Hide comment
@main--

main-- Aug 2, 2017

ELF-based systems like Linux and BSD do embed DWARF section in the binary.

They traditionally do so in debug builds. And it makes sense there.

But moving them to a separate file is perfectly possible:

objcopy --only-keep-debug my_binary my_binary.sym
strip --strip-debug --strip-unneeded my_binary
objcopy --add-gnu-debuglink=my_binary.sym my_binary

This is what I would suggest for release builds on ELF systems.

main-- commented Aug 2, 2017

ELF-based systems like Linux and BSD do embed DWARF section in the binary.

They traditionally do so in debug builds. And it makes sense there.

But moving them to a separate file is perfectly possible:

objcopy --only-keep-debug my_binary my_binary.sym
strip --strip-debug --strip-unneeded my_binary
objcopy --add-gnu-debuglink=my_binary.sym my_binary

This is what I would suggest for release builds on ELF systems.

@est31

This comment has been minimized.

Show comment
Hide comment
@est31

est31 Aug 2, 2017

Contributor

@kennytm thanks for your reply. Could you also reply to these suggestions of mine, you seem to have missed them:

  • caller_location should return a Location struct, not a tuple, for forwards compatibility. You just said that your entire RFC is forwards compatible, but caller_location is clearly not.
  • replacing "magic constants" with #[inline(semantic)] intrinsic functions. Fits much better than constants.
Contributor

est31 commented Aug 2, 2017

@kennytm thanks for your reply. Could you also reply to these suggestions of mine, you seem to have missed them:

  • caller_location should return a Location struct, not a tuple, for forwards compatibility. You just said that your entire RFC is forwards compatible, but caller_location is clearly not.
  • replacing "magic constants" with #[inline(semantic)] intrinsic functions. Fits much better than constants.
@arthurprs

This comment has been minimized.

Show comment
Hide comment
@arthurprs

arthurprs Sep 27, 2017

I'm not fully sold on the importance of debug-info independence.

On platforms without debug info support there are usually other issues, like: binary size concerns, no unwinding, etc..

arthurprs commented Sep 27, 2017

I'm not fully sold on the importance of debug-info independence.

On platforms without debug info support there are usually other issues, like: binary size concerns, no unwinding, etc..

@rfcbot

This comment has been minimized.

Show comment
Hide comment
@rfcbot

rfcbot Sep 29, 2017

The final comment period is now complete.

rfcbot commented Sep 29, 2017

The final comment period is now complete.

@lilith

This comment has been minimized.

Show comment
Hide comment
@lilith

lilith Sep 29, 2017

lilith commented Sep 29, 2017

@main--

This comment has been minimized.

Show comment
Hide comment
@main--

main-- Sep 30, 2017

When I first saw this RFC (or rather its predecessor, semantic inlining), I really hadn't spent a lot of time thinking about the entire topic of panic locations in Rust. But even though the "you're unwrapping None somewhere good luck guessing where" message has certainly annoyed me in the past, I immediately felt that this is not the right approach to this issue.

After some discussion here and writing up my counter-proposal at #2154, I think I can narrow it down to a core issue: I don't think this RFC is solving the right problem. I disagree entirely with two of its design goals:

  • Finding a single function to blame is hard - often, bugs are rather caused by multiple components interacting in unexpected ways. The basic motivaton for both this and #2154 is that panic messages are useless when they don't print the right location, so printing a different location and hoping that it's the right one this time doesn't look like a solid and clean solution.
  • Unstrippable panic messages even in release builds are a drawback, not a feature in my opinion.

main-- commented Sep 30, 2017

When I first saw this RFC (or rather its predecessor, semantic inlining), I really hadn't spent a lot of time thinking about the entire topic of panic locations in Rust. But even though the "you're unwrapping None somewhere good luck guessing where" message has certainly annoyed me in the past, I immediately felt that this is not the right approach to this issue.

After some discussion here and writing up my counter-proposal at #2154, I think I can narrow it down to a core issue: I don't think this RFC is solving the right problem. I disagree entirely with two of its design goals:

  • Finding a single function to blame is hard - often, bugs are rather caused by multiple components interacting in unexpected ways. The basic motivaton for both this and #2154 is that panic messages are useless when they don't print the right location, so printing a different location and hoping that it's the right one this time doesn't look like a solid and clean solution.
  • Unstrippable panic messages even in release builds are a drawback, not a feature in my opinion.
@mikeyhew

This comment has been minimized.

Show comment
Hide comment
@mikeyhew

mikeyhew Oct 6, 2017

Sorry for adding to an already-long discussion, but I'm going to anyway. The solution proposed in this RFC may very well be the best solution to the to the unwrap error messages problem, especially given the amount of discussion that has gone on and the current consensus to merge. And I don't deny that the #[blame_caller] syntax looks elegant and is obvious in what it does, and probably will prove to be useful for lots of other things besides unwrap.

I just want to point out how great it would have been if unwrap had instead been a macro that was allowed in postfix/method position, e.g. result.unwrap!(). Then we would have had error messages with file names and line:column numbers from the beginning. In fact, when I first started learning Rust, I somehow got it into my head that method-style macros were a thing, and was surprised to learn that unwrap was just an ordinary method.

The unwrap! macro is actually not that complicated to implement, by the way. Here's a gist and a playpen link that I put together today. You can do a lot of cool stuff by combining macros and traits!

mikeyhew commented Oct 6, 2017

Sorry for adding to an already-long discussion, but I'm going to anyway. The solution proposed in this RFC may very well be the best solution to the to the unwrap error messages problem, especially given the amount of discussion that has gone on and the current consensus to merge. And I don't deny that the #[blame_caller] syntax looks elegant and is obvious in what it does, and probably will prove to be useful for lots of other things besides unwrap.

I just want to point out how great it would have been if unwrap had instead been a macro that was allowed in postfix/method position, e.g. result.unwrap!(). Then we would have had error messages with file names and line:column numbers from the beginning. In fact, when I first started learning Rust, I somehow got it into my head that method-style macros were a thing, and was surprised to learn that unwrap was just an ordinary method.

The unwrap! macro is actually not that complicated to implement, by the way. Here's a gist and a playpen link that I put together today. You can do a lot of cool stuff by combining macros and traits!

@bstrie

This comment has been minimized.

Show comment
Hide comment
@bstrie

bstrie Nov 1, 2017

Contributor

Firstly, let me echo the sentiment that the RFC is very well-written.

Unfortunately, it seems like a lot of machinery for little gain. I'm supremely worried about the performance impact. Nowadays we have crates reimplementing unwrap to make it friendlier; tomorrow we will have crates reimplementing unwrap to make it faster. Unless this behavior applies only to debug builds, we will be no better off. Worse off, perhaps, because of all the new language machinery.

In addition, I'm not convinced that we ought to be making unwrap friendlier in the first place. Unwrap is something that you occasionally must do, even though you don't want to, which is why its performance still matters. But it would be counterproductive to encourage its use any further.

Contributor

bstrie commented Nov 1, 2017

Firstly, let me echo the sentiment that the RFC is very well-written.

Unfortunately, it seems like a lot of machinery for little gain. I'm supremely worried about the performance impact. Nowadays we have crates reimplementing unwrap to make it friendlier; tomorrow we will have crates reimplementing unwrap to make it faster. Unless this behavior applies only to debug builds, we will be no better off. Worse off, perhaps, because of all the new language machinery.

In addition, I'm not convinced that we ought to be making unwrap friendlier in the first place. Unwrap is something that you occasionally must do, even though you don't want to, which is why its performance still matters. But it would be counterproductive to encourage its use any further.

@dpc

This comment has been minimized.

Show comment
Hide comment
@dpc

dpc Dec 20, 2017

I couldn't read all the discussion, so I'm sorry if it was already said.

This functionality seems also useful eg. for logging when a given function wants to be "invisible" and report/use location of a caller, not itself.

Because of that, I wonder if blame is not too narrow and specific. Just because it was introduced for the given purpose, doesn't mean it should be named like that. #[caller_location] or something would explain what it does, and not what it was introduced for.

dpc commented Dec 20, 2017

I couldn't read all the discussion, so I'm sorry if it was already said.

This functionality seems also useful eg. for logging when a given function wants to be "invisible" and report/use location of a caller, not itself.

Because of that, I wonder if blame is not too narrow and specific. Just because it was introduced for the given purpose, doesn't mean it should be named like that. #[caller_location] or something would explain what it does, and not what it was introduced for.

@kornelski

This comment has been minimized.

Show comment
Hide comment
@kornelski

kornelski Dec 20, 2017

Contributor

caller_location doesn't have a verb, so it sounds odd IMHO, as if the attribute was saying "this is the caller location" (which of course it isn't). #[use_caller_location]? The previous #[implicit_caller_location] wasn't too bad, but #[blame_caller] is shorter and easier to spell :)

#[track_callers] maybe?

Contributor

kornelski commented Dec 20, 2017

caller_location doesn't have a verb, so it sounds odd IMHO, as if the attribute was saying "this is the caller location" (which of course it isn't). #[use_caller_location]? The previous #[implicit_caller_location] wasn't too bad, but #[blame_caller] is shorter and easier to spell :)

#[track_callers] maybe?

@kennytm

This comment has been minimized.

Show comment
Hide comment
@kennytm

kennytm Dec 20, 2017

Member

@pornel #[track_callers] Sounds good to me!

Member

kennytm commented Dec 20, 2017

@pornel #[track_callers] Sounds good to me!

@repax

This comment has been minimized.

Show comment
Hide comment
@repax

repax Jan 1, 2018

I think that the language feature requested in this RFC is both too invasive and too specific. I feel like it's a kludge that, if accepted, will haunt the Rust spec for eternity.

I'd rather see a general solution that allows you to include code in the caller's context that surround the called function (AOP-style but inline), or more work on guaranteeing not just the type of an argument but also its value.

Being able to guarantee values statically (or semi-statically) at the type level is a lot more general and powerful, and something that we would eventually like to tackle. Think of Ada-like value ranges or an effect system encoded in the types of objects and functions.

I'd like to see this proposal postponed until evaluated against long-term goals of the Rust language -- i.e. evolved static guarantees and so on.

repax commented Jan 1, 2018

I think that the language feature requested in this RFC is both too invasive and too specific. I feel like it's a kludge that, if accepted, will haunt the Rust spec for eternity.

I'd rather see a general solution that allows you to include code in the caller's context that surround the called function (AOP-style but inline), or more work on guaranteeing not just the type of an argument but also its value.

Being able to guarantee values statically (or semi-statically) at the type level is a lot more general and powerful, and something that we would eventually like to tackle. Think of Ada-like value ranges or an effect system encoded in the types of objects and functions.

I'd like to see this proposal postponed until evaluated against long-term goals of the Rust language -- i.e. evolved static guarantees and so on.

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jan 25, 2018

Contributor

So, we decided to accept this RFC, but in the meantime there was some more feedback. Let me try to summarize the concerns:

I think these concerns can be summarized as "This RFC doesn't do enough to justify its complexity/cost":

I'm nominating for @rust-lang/lang team discussion to decide if we feel like we ought to back up or can proceed towards implementation.

Contributor

nikomatsakis commented Jan 25, 2018

So, we decided to accept this RFC, but in the meantime there was some more feedback. Let me try to summarize the concerns:

I think these concerns can be summarized as "This RFC doesn't do enough to justify its complexity/cost":

I'm nominating for @rust-lang/lang team discussion to decide if we feel like we ought to back up or can proceed towards implementation.

@joshtriplett

This comment has been minimized.

Show comment
Hide comment
@joshtriplett

joshtriplett Jan 26, 2018

Member

I think we should continue. We don't always want full backtraces, and just knowing the caller location is useful.

Member

joshtriplett commented Jan 26, 2018

I think we should continue. We don't always want full backtraces, and just knowing the caller location is useful.

@whitequark

This comment has been minimized.

Show comment
Hide comment
@whitequark

whitequark Jan 26, 2018

I agree that just knowing the caller is extremely useful. On an embedded device I currently work on, there is no working unwinder right now, and an Option::unwrap() panic might mean... anything. And it is actually not trivial to get a working unwinder, e.g. you need a linker script that properly preserves DWARF sections, and even less trivial to debug an unwinder when for some reason it misbehaves. Not to mention that the unwinder, and especially the DWARF tables, are heavy space-wise.

I don't think that everyone who works with embedded Rust must pay the memory cost of having an unwinder, not to mention the mental cost of getting one to work. Also, there's no pure-Rust unwinders right now, so if one wants to do without the C compiler for the target device, backtraces are just impossible.

whitequark commented Jan 26, 2018

I agree that just knowing the caller is extremely useful. On an embedded device I currently work on, there is no working unwinder right now, and an Option::unwrap() panic might mean... anything. And it is actually not trivial to get a working unwinder, e.g. you need a linker script that properly preserves DWARF sections, and even less trivial to debug an unwinder when for some reason it misbehaves. Not to mention that the unwinder, and especially the DWARF tables, are heavy space-wise.

I don't think that everyone who works with embedded Rust must pay the memory cost of having an unwinder, not to mention the mental cost of getting one to work. Also, there's no pure-Rust unwinders right now, so if one wants to do without the C compiler for the target device, backtraces are just impossible.

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Jan 27, 2018

Member

In the lang team meeting, the overall feeling was that we should stay the course: an improvement here is badly needed, and this RFC continues to be a highly plausible approach. The concerns that have been raised since FCP closed, while legitimate, are best assessed when we have a working implementation to learn from and iterate on.

Thanks again @kennytm for the RFC!

Member

aturon commented Jan 27, 2018

In the lang team meeting, the overall feeling was that we should stay the course: an improvement here is badly needed, and this RFC continues to be a highly plausible approach. The concerns that have been raised since FCP closed, while legitimate, are best assessed when we have a working implementation to learn from and iterate on.

Thanks again @kennytm for the RFC!

@aturon aturon referenced this pull request Jan 27, 2018

Open

Tracking issue for RFC 2091: Implicit caller location #47809

0 of 3 tasks complete

@aturon aturon merged commit f6004d6 into rust-lang:master Jan 27, 2018

pthariensflame added a commit to pthariensflame/rfcs that referenced this pull request Jan 31, 2018

@KasMA1990

This comment has been minimized.

Show comment
Hide comment
@KasMA1990

KasMA1990 Mar 4, 2018

The link to the rendered RFC in OP is broken by the way.

KasMA1990 commented Mar 4, 2018

The link to the rendered RFC in OP is broken by the way.

@Centril

This comment has been minimized.

Show comment
Hide comment
@Centril

Centril Mar 4, 2018

Contributor

@KasMA1990 Thanks, I've fixed it now.

Contributor

Centril commented Mar 4, 2018

@KasMA1990 Thanks, I've fixed it now.

@kennytm kennytm deleted the kennytm:inline-semantic branch Apr 26, 2018

@scottmcm scottmcm referenced this pull request May 16, 2018

Open

Simple postfix macros #2442

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment