-
Notifications
You must be signed in to change notification settings - Fork 185
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
Magic zeroslice!
macros
#3454
Magic zeroslice!
macros
#3454
Conversation
As touched upon in #3455, using If we do not care about the short-hand version of the macro, this point is moot, as one can use |
Hmm, not a fan of the Another approach would be to special-case types that have colliding (u8; $($x:expr),+ $(,)?) => (
$crate::ZeroSlice::<u8>::from_ule_slice(
{const X: &[u8] = &[$($x),+]; X}
)
);
(char; $($x:expr),+ $(,)?) => (
...
); |
Please rebase this on #3453, this seems to iterate on that. |
a8119fb
to
6469df6
Compare
Notice: the branch changed across the force-push!
~ Your Friendly Jira-GitHub PR Checker Bot |
@robertbastian thanks, I had not thought of special-casing in the macro itself. I agree that we should avoid the If these "magic" macros are something that we decide we want, I will implement that change. |
Discussion with @robertbastian: Alternatives to the In both cases users would not be able to (nicely) use Solution i would require less code changes in the short-term, and would not change any @sffc @Manishearth what do you think? EDIT: Summary of what needs to be decided: First, do we want a version of the macros that works by convention (i.e., the name of an associated pub const fn and on which type it lives), or should we leave it at the basic, explicit converter
Personally, I'm somewhat strongly for 2, but I don't care about which 2.x specifically. Leaning towards 2.i. |
My vote is also for 2.i. There aren't that many types that need to be special cased, it's basically just the integers. |
2.i isn't compatible with removing the type annotations, but I don't think that's possible anyway. For that the code would need to use generics, so would have to be completely expressible as a const constructor. That's not possible because we're calling a function on the generic type, which we cannot do in const, but can do in a macro. |
🎉 All dependencies have been resolved ! |
Very much not a fan of ConstAsULE. In the long run, its purpose is served by I think 2.i is an okay route for now. But I'm honestly not convinced we need this magic macro to be so magic. |
I think you can make a type-safe "ConstAsULE" using associated constants: |
I didn't know that, that's cool! Unfortunately, it won't work for our use-case, as they cannot be called in a const context: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d7aec25104078f8bc017fa6bd842b6b9 |
ok, "error: an In general, I'm still quite in favor of making const traits via protocols where we expect a function to exist, and if the function doesn't exist, it's just a compile error, which is fine. The The problem with 2.i is that it is quite invasive. We've had the 1-to-many model for a long time. We can't really enforce 1-to-1 unless we change the traits to give 2.ii seems like a fairly non-invasive solution, and a nice thing is that we can remove the special cases when we have a more general solution. However, the downside of 2.ii is that we still are establishing what is effectively a "canonical" AsULE for each ULE (the one that is the argument of I'm also fine with option 1. The macro improves a bunch on the status quo, and we can wait for const traits and things to stabilize before trying to improve it further. |
Here's the nightly solution with This solution can work on stable if we just say "implement a method named XYZ on the AsULE type", and in the medium term we can export the trait that we want people to const-implement. In cases where we can't implement the function on the AsULE type (impls on external types), we can have a special case in the macro (2.ii). I think this should cover everything, because the only place we can impl AsULE on an external type is in the zerovec crate itself, so we can likewise special-case all those same cases in |
@sffc - I think you swapped 2.i and 2.ii (that's on me - they're not the most visibly distinguishable). 2.ii is the solution wrapping ULEs in newtypes.
I'm not sure I understand (assuming you're talking about 2.i). For cases where there is no "canonical" 1:1 mapping, we just special case the macro, as seen in the most recent wip commit, and use more explicitly named functions (i.e.,
Do I understand correctly that you're proposing exporting a trait, e.g., ConstAsULE, which has a non-const function The main benefit of that solution (I don't see many other differences to the others) being that once const_trait_impl is stable, we can simply switch to that without causing much (or any) headache to the user? I do like that idea! Also, thank you for the sample implementation with the const_trait feature! Is there a reason (perhaps code size?) you put the
I don't want to add ballast to the crate if the consensus is not necessarily clear. It does some add public functions/items that we would have to support once 1.0 happens (be that specialized functions required for the special-cased |
Oops, sorry, 2.i <-> 2.ii. My bad. |
My proposal was that we don't actually export the trait and instead ask clients to implement a conforming function signature (duck typing); then, when able, we can make a breaking change and require the trait itself to be implemented, which won't require any changes to
I think I put the generic on the trait only because by default I avoid generics on trait functions because it means I can't take a |
Some more context: A part of the original motivation behind the "magic" version of this macro was to further improve human-readability of baked data, but after more discussion with @robertbastian, it's a not-so-trivial (if even possible in stable) problem to tackle, given we need to bake the type of I still think it's a "cool" feature to have, and |
I don't immediately see why it's infeasible to bake the I'm happy landing this feature even if it isn't used in databake as a nice-to-have; the guarantee is that we make |
We didn't spend a whole lot of time on this (or maybe @robertbastian spent some more alone), but the gist is that it seems like we'd have to fall back to
Fair enough! |
|
So is the issue that That seems like a solvable problem, and it's a general problem such that it will come to bite us again and again. A couple possible ways I can think of off the top of my head:
Alternatively, if we go with option 1 on this PR, I think we don't need the typename for anything; you just need to explicitly pass the conversion function. But then you have the question of where do you get the name of the conversion function from. |
Another thought: for the bake implementation, what if instead of using ZeroSlice::from_ule_slice(&[
(1i16).to_unaligned(),
(2i16).to_unaligned(),
(3i16).to_unaligned(),
]); and of course |
It's certainly worth keeping in mind for when const traits land, but unless I'm missing something, your example also shows why it won't work in the interim? AFAIK we cannot currently implement const methods on foreign types (e.g. |
Well if we specialize for foreign types, we can avoid databaking |
@@ -0,0 +1,10 @@ | |||
// expand me by cd'ing into properties, then `cargo expand expandme` | |||
|
|||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: eventually should be removed, yes?
utils::wrap_field_inits(&const_from_aligned_conversions, &struc.fields); | ||
let ule_impl = quote!( | ||
impl #ule_name { | ||
#[doc = #ule_doc] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Probably just directly include the doc comment here? There's no interpolation
($aligned:ty, $unaligned:ty, $default:expr, $single:path, $fn_name:ident) => { | ||
#[doc = concat!("Convert an array of `", stringify!($aligned), "` to an array of `", stringify!($unaligned), "`.")] | ||
pub const fn $fn_name<const N: usize>(arr: [$aligned; N]) -> [Self; N] { | ||
let mut result = [$default; N]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thought: could we potentially use MaybeUninit
to avoid the need for $default
? arrays of MaybeUninit
are transmutable to regular arrays
/// `crate::ule::constconvert::ConstConvert<T, T::ULE>`. Unfortunately, you will not be able | ||
/// to reuse this type for your own `ConstAsULE` implementations, since you cannot provide | ||
/// implementations for types outside of your crate. | ||
pub trait ConstAsULE: AsULE { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: doesn't look like we're using it anymore
(I am also in general against adding such a trait)
As it stands currently (with 2.i), I believe there would be some more limitations to the macro:
Basically, the version of the macro implementable in what I would consider a reasonable time frame would be limited to types without generics and an overseeable number of different naming options for the same type, while at the same time requiring to document quite clearly to clients what types can be used in I think these issues would mostly be resolved when What do you think? @robertbastian @sffc @Manishearth |
Would something like this work? |
@sffc How would you support (or decide not to support) cases like Y1, Y2 (for issue 1. from my comment) and Z1, Z2 (for issue 2. from my comment)? |
I generally agree. I don't think this is super super urgent. |
Closing because this is difficult without const traits. See #3454 (comment) for more info |
(thought I posted this earlier but it was still a draft) I see, yeah, all of those cases would need to provide an explicit conversion function. We can cover 90% of use cases now, or stick with the less-ergonomic macro and cover 100% of cases when const traits are stable. I still don't think the explicit conversion function is that bad, so I'm fine with waiting, although according to @Manishearth we may still be waiting for a while. However, we could at least get rid of the |
@sffc #3455 switched to the single-item-conversion-function. The reason that doesn't allow us to also remove the
|
ok. 1 is true for locals but not for statics: We don't care about locales in databake, and I think most use cases are in statics. If we take an approach where we have "named arguments" like |
Clients can always use a local |
Okay, yeah, given that clients can just use a The only case where we might want an optional // works without inner cost
let _: i16 = sum_values(zeroslice![...]);
// does not work without explicit const, and could benefit from `type: i16` argument that creates the inner const
let sub = subborrow_from(zeroslice![...]);
consume(sub); |
Part of #1935.
Depends on #3453.
Potentially makes the
zeroslice!
/zerovec!
macros a lot more user-friendly:The implementation is a bit complex, mostly due to the fact that the
AsULE <=> AsULE::ULE
relationship is many-to-one, as seen, for example, withi32/u32
andRawBytesULE<4>
. This PR works around that issue by adding a new public traitConstAsULE
that redirects theAsULE
type (i.e.,i32
oru32
) to a singular concrete type that implements const conversion betweenAsULE
andAsULE::ULE
. In other words,ConstAsULE <=> ConstAsULE::ConstConvert
is one-to-one. If it wasn't for the many-to-one relationship,ConstAsULE::ConstConvert
could have just been theAsULE::ULE
type always, but this doesn't work as mentioned before.The
zeroslice!
/zerovec!
macros have thus been extended to allow a shorter invocation that uses the default path directly to the type specified inConstAsULE
, which makes the short hand form at the beginning of this description possible.The long hand form that specifies a conversion function explicitly still exists.
The whole issue of many-to-one relationship could be avoided by simply asserting that, e.g.,
u32 <=> RawBytesULE<4>
is the canonical relationship. However, I think that's unfriendly to the user, as the following shows: