Skip to content
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

Implements const_deref feature #49

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

uselessgoddess
Copy link

const_deref feature should add almost all const functionality from std::borrow::Cow

Sorry for the code duplication

@CAD97
Copy link

CAD97 commented Jul 24, 2022

A less duplicated approach (but significantly more complex) would be to use a tt muncher to strip ~const:

macro_rules! cfg_const_trait {
    ( $($tt:tt)* ) => { cfg_const_trait_munch! { [] [$($tt)*] } };
}

#[cfg(not(feature = "const_deref"))]
macro_rules! cfg_const_trait_munch {
    ( [$($done:tt)*] [] ) => { $($done)* };

    // one arm is technically sufficient but more arms reduce recursion depth
    ( [$($done:tt)*] [                  ~const $($todo:tt)*] ) => { cfg_const_trait_munch! { [$($done)*            ] [$($todo)*] } };
    ( [$($done:tt)*] [$a:tt             ~const $($todo:tt)*] ) => { cfg_const_trait_munch! { [$($done)* $a         ] [$($todo)*] } };
    ( [$($done:tt)*] [$a:tt $b:tt       ~const $($todo:tt)*] ) => { cfg_const_trait_munch! { [$($done)* $a $b      ] [$($todo)*] } };
    ( [$($done:tt)*] [$a:tt $b:tt $c:tt ~const $($todo:tt)*] ) => { cfg_const_trait_munch! { [$($done)* $a $b $c   ] [$($todo)*] } };
    ( [$($done:tt)*] [$a:tt $b:tt $c:tt $d:tt  $($todo:tt)*] ) => { cfg_const_trait_munch! { [$($done)* $a $b $c $d] [$($todo)*] } };
    ( [$($done:tt)*] [$a:tt                    $($todo:tt)*] ) => { cfg_const_trait_munch! { [$($done)* $a         ] [$($todo)*] } };
}

#[cfg(feature = "const_deref")]
macro_rules! cfg_const_trait_munch {
    ( [$($done:tt)*] [$($todo:tt)*] ) => { $($done)* $($todo)* };
}

@uselessgoddess
Copy link
Author

Cool. But it doesn't work with const trait together const fn

@uselessgoddess
Copy link
Author

I'm not pro in macros, can you explain what the reason may be

@CAD97
Copy link

CAD97 commented Jul 24, 2022

It's because all this macro is doing is going through the tokens and removing ~const and leaving all of the other tokens. Adding an arm that sees const fn and replaces it with fn is sufficient to allow that to work.

Essentially, all the macro is doing is taking tokens from the right [] and moving them into the left [], but recognizing ~const and const fn to treat them specially. Also, the macro doesn't recurse into {} blocks (which saves a lot of recursive calls), so it has to be used at the level that const shows up; that is, directly around the item whose signature includes ~const.

Since the macro only has to munch over the function declaration (since it jumps the body in on token tree), the following version works fine and is easier to follow:

macro_rules! cfg_const_trait {
    ( $($tt:tt)* ) => { cfg_const_trait_munch! { [] [$($tt)*] } };
}

#[cfg(not(feature = "const_deref"))]
macro_rules! cfg_const_trait_munch {
    ( [$($done:tt)*] [] ) => { $($done)* };

    ( [$($done:tt)*] [~const    $($todo:tt)*] ) => { cfg_const_trait_munch! { [$($done)*   ] [$($todo)*] } };
    ( [$($done:tt)*] [ const fn $($todo:tt)*] ) => { cfg_const_trait_munch! { [$($done)* fn] [$($todo)*] } };
    ( [$($done:tt)*] [$a:tt     $($todo:tt)*] ) => { cfg_const_trait_munch! { [$($done)* $a] [$($todo)*] } };
}

#[cfg(feature = "const_deref")]
macro_rules! cfg_const_trait_munch {
    ( [$($done:tt)*] [$($todo:tt)*] ) => { $($done)* $($todo)* };
}

The additional arms are merely to make macro expansion take $\dfrac{n}{4}$ steps rather than $n$ steps.

The simplest way to allow wrapping impl const Trait would probably be to strip all const rather than just const as part of ~const or const fn. (Delete the two mentions of fn in the middle arm.) Doing something smarter (e.g. preserving const declarations) is not really within the scope of what macros can do.

@uselessgoddess
Copy link
Author

uselessgoddess commented Jul 24, 2022

Thanks, but this is too detailed. I asked because the following code also doesn't work.
But I can't figure out why doesn't recurse into {} blocks

macro_rules! cfg_const_trait_munch {
    ( [$($done:tt)*] [] ) => { $($done)* };

    // one arm is technically sufficient but more arms reduce recursion depth
    ( [$($done:tt)*] [~const $($todo:tt)*] ) => { cfg_const_trait_munch! { [$($done)*   ] [$($todo)*] } };
    ( [$($done:tt)*] [ const $($todo:tt)*] ) => { cfg_const_trait_munch! { [$($done)*   ] [$($todo)*] } };
    ( [$($done:tt)*] [$a:tt  $($todo:tt)*] ) => { cfg_const_trait_munch! { [$($done)* $a] [$($todo)*] } };
}

@uselessgoddess
Copy link
Author

uselessgoddess commented Jul 24, 2022

Since the macro only has to munch over the function declaration (since it jumps the body in on token tree), the following version works fine and is easier to follow:

the following version contains link to my PR

@uselessgoddess
Copy link
Author

Also, the macro doesn't recurse into {} blocks (which saves a lot of recursive calls), so it has to be used at the level that const shows up; that is, directly around the item whose signature includes ~const.

This confused me, because he was still able to remove ~const

@CAD97
Copy link

CAD97 commented Jul 24, 2022

Whoops, I forgot to put a link in at all (it was just [text]()).

The trick about not recursing into {} is that

this version gets stripped
impl<'a, T, U> Cow<'a, T, U>
where
    T: Beef + ?Sized,
    U: Capacity,
{
    cfg_const_trait! {
        /// Borrowed data.
        ///
        /// # Example
        ///
        /// ```rust
        /// use beef::Cow;
        ///
        /// let borrowed: Cow<str> = Cow::borrowed("I'm just a borrow");
        /// ```
        ///
        #[inline]
        pub const fn borrowed(val: &'a T) -> Self
        where T: ~const Beef,
        {
            todo!()
        }
    }
}
but this version doesn't.
cfg_const_trait! {
    impl<'a, T, U> Cow<'a, T, U>
    where
        T: Beef + ?Sized,
        U: Capacity,
    {
        /// Borrowed data.
        ///
        /// # Example
        ///
        /// ```rust
        /// use beef::Cow;
        ///
        /// let borrowed: Cow<str> = Cow::borrowed("I'm just a borrow");
        /// ```
        ///
        #[inline]
        pub const fn borrowed(val: &'a T) -> Self
        where T: ~const Beef,
        {
            todo!()
        }
    }
}

It is probably better to just apply the macro at each level it is needed, rather than adding a recursive call, but it is possible to implement by directly adding the recursion. (This has to be before the fallback arm, or else it will never be taken.)

    ( [$($done:tt)*] [{$($a:tt)*} $($todo:tt)*] ) => { cfg_const_trait_munch! { [$($done)* { cfg_const_trait!{$($a)*} }] [$($todo)*] } };

@uselessgoddess
Copy link
Author

all this time I misunderstood $($t:tt)* and $t:tt. Thanks :)

@CAD97
Copy link

CAD97 commented Jul 24, 2022

Yep, tt stands for Token Tree, and matches an entire paired {}/[]/() and everything in the pair.

Copy link

@CAD97 CAD97 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to reviewers: use "ignore whitespace changes" mode.

src/generic.rs Outdated Show resolved Hide resolved
// for `~const Steak`
// I think that Steak (cooked beef) would contain "cooked" functions
// (computed at compile-time)
// However, `Vec::new()` also has const len/cap, but `std` does not implement it
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thought so far has been to not put const on methods where the type isn't constructible const (or in Vec's case, can only give one answer in const contexts).

I suspect this will relax in the future, but for the time being it allows greater impl flexibility especially w.r.t. future const allocation.

Co-authored-by: Christopher Durham <cad97@cad97.com>
@uselessgoddess
Copy link
Author

uselessgoddess commented Jul 25, 2022

@CAD97 Is panic considered an allowed style in const functions?
For example, unwrap_borrowed etc. can be const

@CAD97
Copy link

CAD97 commented Jul 25, 2022

It depends a bit. If the panic is a static string and does not need any formatting (i.e. panic!("message") and not panic!("message: {context}")), then it can be const.

Current practice seems to be that such methods should be made const only if

  • they currently do not use a formatting panic,
  • they have minimal reason to and are fine commiting not to using formatting panic to provide context, and
  • doing so does not require rewriting the function (e.g. unwrap is a formatting panic and thus not const).

So I'd say unwraps fall under not-const-yet, since unwrap has a connotation of providing a debug formatted context of the failed unwrap target when it fails. In const, use a match instead.1

If our unwrap does not have a Debug bound, however, it seems reasonable to make unwrap const, since it can't do any non-const operations (e.g. Option::unwrap is unstably const as the failure case is dataless, unlike Result::unwrap, where the failure case prints E: Debug).

Footnotes

  1. To match over beef::Cow, .into() -> std::Cow needs to be const. Or, as noted, .as_borrowed().unwrap() works via the unstably const Option::unwrap to strip the formatted context from the panic.

@uselessgoddess
Copy link
Author

  1. To match over beef::Cow, .into() -> std::Cow needs to be const. Or, as noted, .as_borrowed().unwrap() works via the unstably const Option::unwrap to strip the formatted context from the panic.

But then it will prevent non-const implementation.
We'll have to use #[cfg], but it won't work after stabilize const features

@uselessgoddess
Copy link
Author

uselessgoddess commented Jul 25, 2022

I don't find a way to do something similar to if consteval from C++
Maybe now only TryFrom can be const, but Result::unwrap is not const now, users will have to use match

@uselessgoddess
Copy link
Author

But it doesn't look like good code

@uselessgoddess
Copy link
Author

I found a super unstable magic function))

@uselessgoddess
Copy link
Author

uselessgoddess commented Jul 25, 2022

It uses in std with a similar purpose. I can probably trust it

@CAD97
Copy link

CAD97 commented Jul 25, 2022

Note that const_eval_select is explicitly UB if there is any difference in the behavior of the const and nonconst arms.

But yes, since it's not possible at the moment to reconstruct the Owned value in const, just making [unwrap|as]_borrowed const is reasonable: [playground]

#![feature(const_refs_to_cell)] // new

impl<'a, T, U> Cow<'a, T, U>
where
    T: Beef + ?Sized,
    U: Capacity,
{
    #[inline]
    pub const fn as_borrowed(&self) -> Option<&'a T>
    where
        T: ~const Steak,
        U: ~const Capacity,
    {
        if self.is_borrowed() {
            Some(self.borrow())
        } else {
            None
        }
    }

    #[inline]
    pub const fn unwrap_borrowed(self) -> &'a T
    where
        T: ~const Steak,
        U: ~const Capacity,
    {
        match self.as_borrowed() {
            Some(borrowed) => borrowed,
            None => panic!("called `unwrap_borrowed` on an owned `beef::Cow`"),
        }
    }

    #[inline]
    pub const fn is_borrowed(&self) -> bool
    where
        U: ~const Capacity,
    {
        self.capacity().is_none()
    }

    #[inline]
    const fn borrow(&self) -> &T
    where
        T: ~const Steak,
    {
        unsafe { &*T::ref_from_parts::<U>(self.ptr, self.fat) }
    }

    #[inline]
    const fn capacity(&self) -> Option<U::NonZero>
    where
        U: ~const Capacity,
    {
        U::maybe(self.fat, self.cap)
    }
}

@uselessgoddess
Copy link
Author

But yes, since it's not possible at the moment to reconstruct the Owned value in const, just making [unwrap|as]_borrowed const is reasonable:

I thought the same thing

Comment on lines +10 to +14
#[cfg(feature = "const_deref")]
use core::marker::Destruct;
use core::marker::PhantomData;
#[cfg(not(feature = "const_deref"))]
use core::marker::Sized as Destruct;
Copy link
Author

@uselessgoddess uselessgoddess Jul 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CAD97 is there anything more invasive than Sized?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where Self: Sized is in fact a trivial bound that doesn't add any restrictions on the ungated impl, if that's what you're asking.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I just don't know anything more trivial.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

T: ?Sized is the only weaker bound than T: ?Sized currently, or just T:.

Copy link
Author

@uselessgoddess uselessgoddess Jul 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but we can't create alias for this . On the other hand, Destruct used in one function.

Comment on lines +74 to +75
// fixme: it should be unsafe :)
const fn from_parts(ptr: NonNull<T::PointerT>, fat: usize, cap: U::Field) -> Self {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maciejhirsz uses a lot of unsafe in the code, believing that Cow is created only in borrowed/owned
In this case, it is reasonable to make from_parts unsafe

src/generic.rs Outdated
let Cow { ptr, fat, cap, .. } = self;
(*ptr, *fat, *cap)
}

// fixme: required `borrowed_from_raw_parts` - it has bug with the processing of structs
Copy link
Author

@uselessgoddess uselessgoddess Jul 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// fixme: required `borrowed_from_raw_parts` - it has bug with the processing of structs
// fixme: required `from_parts` - `cfg_const_deref` has problem in the processing of structs
``

src/generic.rs Outdated
where
T: ~const Steak,
U: ~const Capacity,
<T as ToOwned>::Owned: ~const Destruct
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is necessary? rustc suggest use it.

src/generic.rs Outdated
Comment on lines 309 to 310
let (ptr, fat, cap) = Self::std_into_parts::<T, U>(cow);
Self::rt_from_cow(ptr, fat, cap)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe there is a way

@CAD97
Copy link

CAD97 commented Jul 27, 2022

(The moment the code uses core::intrinsics is when it goes outside of the scope I'm comfortable reviewing. I'm absolutely not comfortable making an argument for the soundness of using const_eval_select, and relying on it makes reviewing the rest of the signatures pointless, since they're lying and being supported by const_eval_select.)

(I'm still not comfortable using const_eval_select even with the following change, but I'd be marginally more comfortable with moving the select into owned_{into|from}_parts using it with the argument calling the functions in a const context is necessarily UB to justify the select.)

(Generally, any feature without a tracking issue is a no-touch.)

@uselessgoddess
Copy link
Author

Oh, no. It is sad))

@uselessgoddess
Copy link
Author

uselessgoddess commented Jul 27, 2022

May be users will be use .as_borrowed().unwrap() or .magic_into_std() in const context?
Also, const From/Into<std::Cow> will be added later

@uselessgoddess
Copy link
Author

I re-read Safety zone in const_eval_select docs. Perhaps creating some const_eval function makes sense. After all, it will be the code that uses it will work the same in rt and ct.
But this feature does not have tracked issue. I now remove const From/Into<std::Cow>

@uselessgoddess
Copy link
Author

@CAD97 why some functions require const_refs_to_cell feature?

@CAD97
Copy link

CAD97 commented Jul 28, 2022

Roughly, mutability behind an indirection (&mut) is unstable. &Cell presents the same questions as &mut, so is also unstable. In theory, what is actually needed/desired to be gated is doing a mutation through a reference, but just forbidding creation of mutable references is easier. (At least for &mut; &Cell is trickier to explain, but actually more complicated than &mut, since you can model unique &mut as just passing the value in and out.)

Starting with &T for an arbitrary T is fine, as this serves as an observation that T is free of cells; going from owned T to &T is gated.

Cow<'_, _, _> is potentially cell-containing via the cap: U::Field field, as is Option<U::NonZero>.

Option::is_none can be open coded with a match to avoid requiring const ref to (maybe) cell, and unwrap_borrowed can inline everything to only work on the generic types by value. But if we're enabling ~const anyway, const_refs_to_cell is fine to also enable; it's very likely to stabilize before ~const, and we're not actually using any of the in-question parts anyway (as evidenced by being able to inline to silence the gate).

As the feature gate error says, see rust-lang/rust#80384 for more information.

Comment on lines +74 to +75
// fixme: it should be unsafe :)
const fn from_parts(ptr: NonNull<T::PointerT>, fat: usize, cap: U::Field) -> Self {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// fixme: it should be unsafe :)
const fn from_parts(ptr: NonNull<T::PointerT>, fat: usize, cap: U::Field) -> Self {
const unsafe fn from_raw_parts(ptr: NonNull<T::PointerT>, fat: usize, cap: U::Field) -> Self {

Obviously the change needs to be propagated. Even though field access is safe and it is formally acceptable to have non-exported safe functions with safety requirements, it's much better practice to mark them unsafe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants