-
Notifications
You must be signed in to change notification settings - Fork 354
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
Extern ref types #586
Extern ref types #586
Conversation
68098dd
to
bdbf500
Compare
packages/std/src/deps.rs
Outdated
@@ -22,3 +22,43 @@ impl<S: Storage, A: Api, Q: Querier> Deps<S, A, Q> { | |||
} | |||
} | |||
} | |||
|
|||
pub struct DepsMut<'a, S: Storage, A: Api, Q: Querier> { | |||
pub storage: &'a mut S, |
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.
Not sure I totally understand the problem (or the full space of the problem) but, if you want to provide discretionary mutable access to Storage
, why not wrap Storage
itself in a RefCell
, i. e.:
pub storage: RefCell<&'a S>
That way, you can continue to use Deps
as before (i.e. like &deps
), and just borrow_mut()
storage
when mutable access is required.
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.
I believe I would need RefCell<S>
to be able to get a &mut S from &RefCell.
The cell can't give out something it never had
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.
One reason motivating this (before removing generics) was that previously the caller had to own S and Q to be able to give a reference out. If the owner has &mut S and &Q that is all the access the handle function needs, but it could not actually call it. (I hit this trying to build a test framework).
Once I started using references, the idea of removing generics and using &dyn came up and got rid of a lot of noise
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.
I believe I would need
RefCell<S>
to be able to get a &mut S from &RefCell.
I see, and you're right. And owning those objects is precisely what you're trying to avoid.
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.
Kept thinking about this... I guess what I don't like (besides the multiplication of entities), is that this looks like a Rust "antipattern". I mean, in Rust the access mode (mut
vs. const
) is usually resolved by modifiers, or through a method call (as_mut()
and such); not through object / struct definition.
Also, wouldn't make sense to just own Storage
, or a copy of it? You can still have references for Querier
and Api
, but wouldn't make sense to pass Storage
by value (or inside a RefCell
if needed)?
I know this is against what you're trying to do here. Just pondering.
And, finally, maybe this can be resolved by passing a reference to a generic container, like Deps
, but one that is not built on purpose, just to pass references, but is the original handler / container for those types. Not sure if this can be done given the current architecture / design decisions, though; and, if this wouldn't bring their own share of problems with multiple mutable access, borrowing, etc.
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.
API looks good to me, and it's clearer/simpler than the previous one.
My only concern would be the general one I posted as a comment, i.e. if mutability selection could be restricted only to the storage
member somehow.
Haha, I see Mauro is stuggeling with this problem in a similar way I do. This concern is not about getting it compile, but this feeling that when things get super complicationd, you are probably doing something wrong. It feels like Rust is screeming don't try to code Java with me. But looking at the facts, I think we can agree on the following:
This at the end of the day make Ethan's solution the only one I see working. And I send a lot of time thinking about alternatives. One change I'd like to propose (I can send a PR for the implementation) is to make the set of references the primary structure fn _do_query<M, E>(/* … */) -> ContractResult<QueryResponse> {
// ...
let storage = ExternalStorage::new();
let api = ExternalApi::new();
let querier = ExternalQuerier::new();
let deps = Deps {
storage: &storage,
api: &api,
querier: &querier,
};
query_fn(deps, env, msg).into()
} We then can have a Nice change using |
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.
A few things here. Biggest wish is the renaming (and change of perspective) as described in #586 (comment)
packages/std/src/traits.rs
Outdated
#[derive(Copy, Clone)] | ||
pub struct QuerierWrapper<'a> { | ||
querier: &'a dyn Querier, | ||
} |
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.
Why did you create this extra type?
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.
To solve a headache.
The Querier
trait was implementing a lot of automatic functions (beyond the one we implement). These are generic functions. &dyn Querier
as a trait objects does not support traits with generic functions (as there is not a fixed vtable with all possible trait functions). You can look in the history, there was a place where I pulled out S and A to &dyn Storage and &dyn Api, but left Querier. I had to add this type to get Querier to compile.
All those Querier "helper functions" can no longer be on the trait. Wrapping it in a struct seemed the easiest way to add it. But I guess using a newtype pub struct QuerierWrapper<'a>(&'a dyn Querer)
is actually a clearer representation of how we wrap - like Binary
/CanonicalAddr
wrap Vec<u8>
.
Secondly, we can adjust naming. Maybe QuerierWrapper
=>Querier
(as this is what contract devs will be seeing), and Querier
=> QuerierTrait
or such, which is only used internally by core devs. Same way to make Deps
, DepsRef
and simplify the naming in the public API by giving the internals a more clunky naming.
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.
I did the rename in a7efaa3
. Happy to revert it if you don't like it, but I think this solves the issue with a cleaner public API.
Good analysis. I would like to challenge your point 3 above. There's a way to handle a reference with dynamically checked borrowing rules, and that's a Mutably borrowing from your code above, what I'm proposing is something along:
So, basically, passing a reference to This still copies
at initialization. Which is the logical thing to do, by the way. |
Mauro, I explicitly only want to pass in the minimal ownership needed - not move in storage (or even If I run recursive queries on a contract, that would be allowed in the Rust type system if they all have The point is this is not a normal struct, it really is just combining three args into one as they are commonly passed together. Simon did a spike to just pull them into 3 arguments and it made the API much messier. There has been quite some fiddling around to look for designs here, and supporting 2 different structs in the public API ( |
True. And, if the goal is precisley to make the API simpler, this is an important point. I tend to forget we're dealing with an API here, and that it must not only work with the existing code base, but also be easy to use in future contracts / for future users. |
304fd60
to
5e24955
Compare
The issue with |
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.
Nice, we are getting there :)
Thanks for the explanation around QuerierWrapper
. I'd revert a7efaa3. I think the old name was better, or at least more consistent with the rest. We'll find a way to nicelify it. We can use a newtype or a struct, no real preference. The newtype might be better to express the intend you explained.
packages/std/src/deps.rs
Outdated
} | ||
|
||
#[derive(Copy, Clone)] | ||
pub struct DepsRef<'a> { |
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.
I'd call this Deps
and the mutable version DepsMut
. I think those names are better than the "Ref" suffix. This also uses the typical immutable default.
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.
Okay, doing that now, then preparing for merge
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.
Alright, cool. Happy when CI is
contracts/burner/src/contract.rs
Outdated
@@ -1,12 +1,12 @@ | |||
use cosmwasm_std::{ | |||
attr, Api, BankMsg, Binary, Deps, Env, HandleResponse, InitResponse, MessageInfo, | |||
MigrateResponse, Order, Querier, StdError, StdResult, Storage, | |||
attr, BankMsg, Binary, Deps, DepsMutMut, Env, HandleResponse, InitResponse, MessageInfo, |
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.
attr, BankMsg, Binary, Deps, DepsMutMut, Env, HandleResponse, InitResponse, MessageInfo, | |
attr, BankMsg, Binary, Deps, DepsMut, Env, HandleResponse, InitResponse, MessageInfo, |
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.
Yup, fixed this. And also rebuilt the test contract.
Getting ready to merge.
Closes #584
Adding types and showing the diff for hackatom contract.
It was just changing the function definitions, and then search replace in the test code:
&mut deps
->deps.as_mut()
and&deps
->deps.as_ref()
Update: After some nice conversations with an advanced Rust coder, I realized I could remove all the
<S: Storage, A: Api, Q: Querier>
generics in contract code if I just use eg.api: &dyn Api
instead ofapi: &A
. This definitely remove a lot of visual complexity from the contracts and makes them more approachable for Rust beginners.This does involve some more changes to the contract code, but mainly removing complexity.
I would love some feedback when you can review. I think the newer contract code is much easier for devs starting with CosmWasm. And note that the vm code doesn't have to change at all, so this is backwards compatible with the wasm code generated from 0.11 (so we can stick with
cosmwasm_version_4
)