-
Notifications
You must be signed in to change notification settings - Fork 48
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
Improve block2
#168
Comments
|
I'm starting to suspect that the naive approach of adding The following attributes affect blocks in particular:
Which seem to suggest that maybe we should instead have these four: |
Although, escaping closures can’t capture mutable references, while non-escaping closures can, so maybe we can allow |
I think we do want some way to have E.g. basically all |
A |
Design idea: struct Inner<'a, A, R, F> { ... }
pub type Block<'a, A, R> = Inner<A, R, dyn FnMut(A) -> R + 'a>;
pub type BlockMut<'a, A, R> = Inner<A, R, dyn FnMut(A) -> R + 'a>;
pub type BlockOnce<'a, A, R> = Inner<A, R, dyn FnOnce(A) -> R + 'a>;
pub type BlockSend<'a, A, R> = Block<A, R, dyn Fn(A) -> R + Send + 'a>;
// And so one for `BlockSendMut`, `BlockSendOnce`, `BlockSync`...
fn takes_sendable_block(block: &BlockSendSync<'static, (u32,), u8>) {}
fn takes_noescape_block(block: &Block<'_, (u32, u32), u32>) {}
fn takes_mutating_block(block: &mut BlockMut<'static, (), ()>) {} This is kinda nice since we avoid a proliferation of traits that way. Though reconsidering, maybe we can actually make it as simple as: struct Block<F> { ... magic ... }
fn takes_sendable_block(block: &Block<dyn Fn(u32) -> u8 + Send + Sync>) {}
fn takes_noescape_block(block: &Block<dyn Fn(u32, u32) -> u32 + '_>) {}
fn takes_mutating_block(block: &mut Block<dyn FnMut()>) {} That way, we should be able to support all possible configurations, and then we can let |
EDIT: Moved to #573. |
Ideally, we'd be able to just write something like: #[method(myMethodTakingBlock:)]
fn my_method(&self, block: impl IntoBlock<dyn Fn(i32) -> i32 + Send + '_>);
// impl<F: Fn(i32) -> i32 + Send> IntoBlock<dyn Fn(i32) -> i32 + Send + '_> for F { ... }
obj.my_method(|n| {
n + 4
}); Though I think that may require us to merge the
Which means there's no longer a clear dependency relation between these two crates :/ We might also just be able to hack it in the Alternatively, allowing |
On further reflection, I think In Rust, you are allowed to do two things with an Any C code that takes a block fundamentally cannot uphold these guarantees without us writing extra glue code. This is due to how destructors in blocks are implemented; you (or the compiler) calls I guess we could implement it such that when calling the block, we read the memory of it, and in the destructor we check whether the block has been called or not. For that we'd need to store a piece of state on whether or not the destructor has been run. Which... Is trivially doable in safe Rust with an pub fn fnonce_to_fnmut(f: impl FnOnce()) -> impl FnMut() {
// Or with `ManuallyDrop`, if you wanted to micro-optimize
// with the assumption that the block is always called.
let mut container = Some(f);
move || {
// Or with `unwrap_unchecked`, if you wanted to micro-optimize
// with the assumption that it is only called once
if let Some(f) = container.take() {
f()
}
}
} Given that the implementation above is trivial and safe, and that it also varies what kind of micro-optimizations you are willing to make (depends on the guarantees of the API you're calling), I don't think we should attempt to provide a block wrapper around |
Similarly to #563, I'm unsure if mutability is a good idea safety-wise? Though then again, in most usages of blocks they probably are mutable, it's just that blocks are still reference-counted, and it's very easy to then create e.g. a block calling itself (re-entrancy) (which would be UB if it was a |
Regarding blocks as objects: Can we convert Also, pretty sure blocks cannot be used in So is there really any value in blocks being usable as objects, other than just to implement everything the spec says? Maybe there are instances of attributes stored in |
Hello, just want to share another model for blocks. Which I kinda like how it works. #[repr(transparent)]
pub struct Block<Sig, Attr = NoEsc>(ns::Id, PhantomData<(Sig, Attr)>); and use Open parameter Next it can be StackBlocks only supported for block.call() doesn't require () (unit) inside. But for constructors you have to specify N - number of arguments. |
Thanks, always open for feedback!
Yeah, there's basically two issues regarding lifetimes with my current implementation:
I've tried to document that somewhat, and there's UI tests for each of these, but these issues are kinda unsolvable in current Rust as far as I know.
I'm not sure I understand the value of
I've called your
I initially didn't think this was possible, but checking again, I see that it is, have opened #575 to track that.
Yeah, I think it's better to have a trait so you can just use |
Nope, it is opposite. It allows to model closure consumptions. So vars borrowed in them can be used again. This is definition: This is call with closure consumption: And this actual example of usage: |
Yep, I had this before. But failed to model external blocks provided by frameworks. (I had to transmute them, which I did like). |
Hmm, can you give an example of an external block that didn't work / required a transmute with the lifetime scheme? |
|
Hmm, that's escaping, right? I think that's possible to model as a extern "C" {
#[link_name = "_nw_parameters_configure_protocol_disable"]
static NW_PARAMETERS_DISABLE_PROTOCOL: &Block<dyn Fn(nw_protocol_options_t) + Send + Sync + 'static>;
} |
hmm... |
If extern "C" {
#[link_name = "_nw_parameters_configure_protocol_disable"]
static NW_PARAMETERS_DISABLE_PROTOCOL: &Block<dyn for<'a> Fn(&'a MyReference) + Send + Sync + 'static>;
// Desugared from `dyn Fn(&MyReference) + Send + Sync`
} Though as I noted above regarding lifetimes, you might have trouble calling this block from Rust, as the lifetime there is too generic for the (current) trait implementations. An implementation that solves this specific case is possible, it just isn't in the most generic fashion yet. // This is possible, and would allow calling the above
impl<A> Block<dyn for<'a> Fn(&'a A) + Send + Sync + 'static> {
fn call(&self) { ... }
}
// but would conflict with more generic impl
impl<A> Block<dyn Fn(A) + Send + Sync + 'static> {
fn call(&self) { ... }
}
// (this generic impl effectively creates
impl<'a, A> Block<dyn Fn(&'a A) + Send + Sync + 'static> {
fn call(&self) { ... }
}
// which is not generic enough).
// Ideally we need
impl Block<dyn for<A> Fn(A) + Send + Sync + 'static> {
fn call(&self) { ... }
} |
Yes, that is exactly why I have to use |
Hmm, I see what you're doing, here's my playground for trying to retrofit it into A problem with your current design is indeed that the signature is just that, a signature, and the user can still put anything that implements |
I haven't given this crate nearly as much love as it deserves!
In particular, it would be nice to allow mutating blocks, e.g.
FnOnce
andFnMut
should becomeBlockOnce
andBlockMut
.Also, I'm not certain the way
ConcreteBlock
is put on the stack is sound? And what about lifetime of the stuff used in the closure?Issues in
block
cc
rather thangcc
. SSheldon/rust-block#8block
crate into this repo #18.FnMut
in blocks? #571.block2 0.2.0-alpha.6
: ImplementingEncode
/RefEncode
is bothersome and not always possible SSheldon/rust-block#16Work on this in other projects
cidre
's blocksblocksr
The text was updated successfully, but these errors were encountered: