-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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: fmt::Show and fmt::String guidelines #565
Conversation
I think it's worth elaborating on the problem of nesting one type's "for human consumption" into a generic type's "for human consumption". Imagine that cargo::Package implements a The person who implemented |
@aturon I'm struggling with how to decide when type information can be left off of a Can you suggest some straw-man guidelines that differentiate between I'd like to flesh that out and get it added to the RFC 😄 |
Is it possible to only show type information for the top level? The only exception would be when you have dynamic polymorphism, in which case you'd need to output the specific type, eg. |
Why? In that light, I'd say integer suffixes can be left off because the compiler can almost certainly infer them when building the unit test. Also, how do we |
@Diggsey Not without multiple traits (or an additional But perhaps more importantly, I think it's clear that |
In Ruby:
In Node:
@aturon I don't see a clear way to implement this with the existing |
@wycats Yeah, I'm also uncomfortable with how vague the initial proposal is here, and hoping that we can iterate toward something crisper. One problem is to balance uniformity against readability. It's hard to see how to avoid special cases. For example, the outcome I'd like for
Those are some ad hoc rules! But I think there is a clear intent behind them: to make it easy to make sense of compound data of various kinds without overwhelming debugging output with every last bit of type information. Essentially, types at the leaves are left off, as are types of "trivial" wrappers (like smart pointers), but composed things like Compare: Vec {
MyStruct { f1: 0u32, f2: "some\nstring", f3: MyNullaryVariant },
MyStruct { f1: 1u32, f2: "", f3: MyUnaryVariant(0.3f32) }
} to [MyStruct { f1: 0, f2: "some\nstring", f3: MyNullaryVariant },
MyStruct { f1: 1, f2: "", f3: MyUnaryVariant(0.3) }] I feel like the second version is much less noisy, but hasn't lost any interesting information. I would love some help making this more crisp, if others share similar preferences. |
@wycats Well one way would be to have the Show trait always omit the type if known statically, and then have a shortcut way to "show" both a value and it's compile-time known type. Also, I don't think your example is necessarily true, I'd prefer this:
Than this (especially for longer lists, and doubly for when the type is more complex, eg. Person< Some type params... > or something):
|
@aturon quick clarification: When you say "String" there, you mean every kind of String ( |
@Diggsey can you show a sample implementation of a sample |
@aturon you mean at the top-level only, right? |
Unless you're debugging an overflow or precision issue ;) |
Yes.
No, I mean at any level. See the example -- Given that we want And anyway, for nested structures I think it's still useful to see the struct name: Foo { f1: Bar { b1: 0, b2: "hello" }, f2: Baz(3) } |
My point is just that this information is readily available elsewhere, especially if we print the type name for any enclosing structs. |
I mean that the CONTENTS of a struct are not printed as you would normally construct them. Only the structure. |
And my point is that sometimes you're poring through tons of trace logs and going back to the original definition is very time consuming if you actually care. |
@wycats: I just looked at the python implementation, it simply maintains a list of containers that currently have an active |
@dgrunwald that seems... not ideal for a Rust |
I'm afraid I still don't understand. The contents of the structure are printed according to whatever their
Maybe I'm way off base, but it seems like if we're printing the surrounding type information -- which struct and field is this going into -- it should be very very easy to discover the type information. That's the sense in which it seems highly redundant to me. I suppose in some cases being reminded of the type information in debug output could help you make a connection you wouldn't otherwise? Is that what you have in mind? |
@wycats: The other option would be to change the trait to pass around a |
I should have laid this out more explicitly, but while I can see the benefit of what you're suggesting, I worry that it would lead to much more verbose debugging output in general. Further, in a lot of cases in practice it's not really possible to do this, because the internals of a data type are private; at the very least, the debugging output might involve a call to a constructor, etc. That said, I think in practice the conventions I'm suggesting will put you in the ballpark; I mostly wanted to clarify that it's not a hard-and-fast rule.
Agreed. |
@wycats Types would implement Show such that they'd never output type information, and to actually display a value when debugging it would do this:
Although obviously it would be nicer if there was a better syntax (maybe there could be a format specifier which automatically appended the type information or something) |
cc @seanmonstar |
cc @nick29581 |
Regarding not printing integer suffixes: I see the pain. But I also have cases where I want to see them. Take this simple example: fn do_things<T: Show>(t: T) {
debug!("doing things with {:?}", t);
}
do_things(1is);
do_things(1u64);
do_things((1u8, 2u8)); In this case, I'd need to be able to see what I got, since I don't have the actual type definition right there. But I definitely see the grossness in printing it for a big vector. Could we make use of the alternate If I had my way, I'd go down the route of making everything much noisier in debug outputs. debug!("{:?}", "foo"); // &"foo"
debug!("{:?}", "foo".to_string()); // String { "foo" }
// etc |
However, Rust source is a notation that's good for concisely and accurately describing Rust values, and one that Rust programmers are likely to be familiar with. Even if a |
Other than
Since this takes self by value, the original struct would be lost if the function fails, hence it is returned in the tuple of the "error" value so you can recover it. Another possibility is to add the original object as a parameter of the error object, but this isn't ideal since moving the original object out of the error would render the error unusable after the move, preventing you from retaining it for logging purposes or propagating it up the stack. Further, the original object has lifetime constraints as well as a destructor, so it would restrict the life of the error object unnecessarily. In short, I agree that the error value of |
(Note though that this requirement is only imposed for |
I find I only use unwrap to ease writing tests. Having to impl Display as On Thu, Jan 22, 2015, 1:21 PM Niko Matsakis [email protected]
|
FWIW, I'm finding the arguments in favor of However, I think @alexcrichton may have some further thoughts. |
I think it would be nice to have all three of |
@aturon I think that w/ specialization, there can be a default |
After reading over these comments I reached the same conclusion as @carllerche, which I believe for now sounds like we should to back to |
So earlier I misread this and said I agreed. But actually my view is that of @alexcrichton, namely that with specialization we'd prefer |
Hm, one downside of this that I forgot is that the quality of many error messages will go down significantly in some cases. I think that "avoiding panicking as much as possible" is certainly an important method of using the standard library, but it's by no means the only way of using it! An example is that |
I think this is unavoidable given the tools we have available today, but I consider this to be the correct behavior in Ruby:
Printing out escape sequences in |
I don't believe that the ruby exception throwing is the correct analogy to if let Err(e) = risky_business() {
panic!("attempted to perform risky business -- failed with {}; context={}; more-context={}",
e, foo, bar);
} In which case, a good Imagine the case where the The argument that is being used here is that error types will implement Simply implement Defaulting to tl;dr Unwrapping a |
@carllerche For tests, couldn't you implement an |
My usage of unwrap in tests is not for assertions, it's to destructure data such that if it is not in the expected structure, it will fail hard. In the cases where the unwrap fails, I simply want debug info so that I can figure out what went wrong. Again, one should not be using |
@carllerche I am talking about some trait that you could implement that expresses precisely what you're trying to do: "I want to to destructure a Result such that if it is not in the expected structure, it will fail hard and use the Debug trait to display the Error variant" |
When outputting errors there really are 2 cases.
if let Err(e) = risky_business() {
panic!("attempted to perform risky business -- failed with {}; context={}; more-context={}",
e, foo, bar);
} In this case, you definitely want the
|
@carllerche I think the correct solution given specialization is probably something like an |
To add to this, I’m also very concerned about I don’t care about the error message when using If a nicer error message is desired, |
@SimonSapin arguably a type like |
@alexcrichton IIRC the documentation suggests to always use |
This is currently required by `Result<T, ImageError>::unwrap`. Fixes build errors in test files.
As @tbu- said, it’s so that I can use I’ve experimented to extend the |
I’ve also used |
As far as I can tell, there's widespread agreement that we should revert to bounding by @alexcrichton would you like to make that change, or shall I? |
@tbu- yes and I believe the general practice is to use custom error types instead of @SimonSapin Yes currently I've created a PR for this change: rust-lang/rust#21558 |
By the way, (though this might be getting off-topic, sorry) I don’t really see the point of the |
The intent was to standardize on functionality that production error types would implement; it also provides the cause chaining hooks that @mitsuhiko's ideas work from. FWIW, I would like to move forward with some of his ideas, but they basically are back-compat since they just use the |
A recent RFC split what was previously
fmt::Show
into two traits,fmt::Show
andfmt::String
, with format specifiers{:?}
and{}
respectively.That RFC did not, however, establish complete conventions for when to implement which of the traits, nor what is expected from the output. That's what this RFC seeks to do.
Rendered