-
Notifications
You must be signed in to change notification settings - Fork 795
refactor: make contract abstract over Borrow #2082
Conversation
ethers-contract/src/contract.rs
Outdated
} | ||
|
||
impl<M> std::ops::Deref for Contract<M> { | ||
pub type Contract<M> = ContractInternal<std::sync::Arc<M>, M>; |
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.
type aliases ensure backwards compatibility
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.
this makes it easier indeed,
especially for http provider, which actually could be made Clone
tx, | ||
client: Arc::clone(&self.client), // cheap clone behind the Arc | ||
client: self.client.clone(), |
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.
we could make ContractCallInternal
borrow our B here, by introducing a lifetime to it
I am concerned about the practicality of the B: Clone
bound, for borrow types without clone. are there commonly used borrow types that are NOT clone? 🤔
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.
It can be the case when B: Borrow<M>
is equal to M
and M
is not Clone, since Borrow
has a blanket implemention for any T: ?Sized
. Otherwise it would mostly be the standard smart pointers. So I'd say if all our Middleware
implement Clone
this is fine
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.
unfortunately, not all of our middleware implement Clone, IIRC. e.g. I think SignerMiddleware<LedgerSigner, _>
does not 🤔
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.
Ah yeah right, I guess this will need to be specified in a doc somewhere that it is recommended wrapping M
with a smart pointer or using references to avoid cloning data or for cases where that it is actually not possible
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'm going to make a couple toy examples for when B
is M
or &M
. (&M
is Clone
however, so that should be fine)
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've also confirmed that NonceManagerMiddleware
is not Clone
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.
so the place where I user would run into this is:
- instantiating a contract with an owned non-clone middleware
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.
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.
Is that a big problem? Can't we pass these Middlewares by reference, instead of owned? And otherwise, just Arc them?
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.
Is that a big problem? Can't we pass these Middlewares by reference, instead of owned? And otherwise, just Arc them?
it's a not-immediately-obivous issue that users will run into and ask about. It compounds with the ambiguity caused by auto_imp(&, Box, Arc)
to make this more-powerful API extremely difficult to use
if you call ContractInstance::new(address, &provider)
you get a type ambiguity around M
. is M
Provider
or &Provider
. Both implement Middleware
and both can be borrowed from &Provider
. Same ambiguity for Arc
The follow up work (will copy this to original post) to get this usable by beginner rustaceans is something like:
1. list further abstraction targets
2. refactor to use borrow
3. provide backwards compatible type aliases like `type Contract = ContractInstance<Arc<M>, M>`
4. remove `auto_impl(&, Box, Arc)` on `Middleware`
5. write migration guide
6. deprecate backwards-compatibility aliases
7. remove aliases
currently checking out the test breakage. looks like this will affect type inference |
the test failures are all driven by failed inference on this is only an issue when switching the middleware connected to a contract, and the solution is to write out |
workaround:
|
anyone have a better name than |
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 like this change,
using Borrow
should allow us to create a Contract
that uses a reference to a provider (&M
) right? do we have an example for this.
so it's possible to
- connect ws
- create and consume
Contract
with&ws
- return ownership of
ws
so "Arc" should no longer be mandatory for every "Contract" interaction, right?
ethers-contract/src/contract.rs
Outdated
/// `Contract` is a [`ContractInternal`] object with an `Arc` middleware. | ||
/// This type alias exists to preserver backwards compatibility with | ||
/// non-abstract Contracts. | ||
/// | ||
/// For full usage docs, see [`ContractInternal`] | ||
pub type Contract<M> = ContractInternal<std::sync::Arc<M>, M>; |
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.
should we perhaps mark the aliases as deprecated?
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.
adding a deprecated tag results in close to 200 warnings in the ethers codebase. To avoid noise in the PR, I am adding in a commented-out deprecated attribute. I'll uncomment it and change all of our internal usage as a last step.
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.
update:
see top comment for future plans wrt this. Aliases will not be deprecated in this PR. We will deprecate after we have a straightforward and beginner-friendly migration plan
cc9762e
to
f4570ca
Compare
tx, | ||
client: Arc::clone(&self.client), // cheap clone behind the Arc | ||
client: self.client.clone(), |
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.
Is that a big problem? Can't we pass these Middlewares by reference, instead of owned? And otherwise, just Arc them?
Please see updated notes at top of comment thread. I believe this is ready for final review and 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.
Supportive - pending @mattsse
Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
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 think this is step in the right direction.
I guess there could be some quirks wrt Arc and its auto_impls but we can figure things out when we encounter them.
Motivation
Contracts currently hold
Arc<M>
and have boundwhere M: Middleware
. This requires managing arcs across a codebase, which imposes unpleasant requirements on binariesSolution
B: Borrow<M>
instead of holding anArc<M>
. This allows contracts to be used directly with Cows, non-ARCed middleware, Boxes, other smart pointers, and anything else that uses theBorrow
trait.ContractInternal
and create a type alias under the old name, for backwards compatibilityExample use case:
Lazy<Provider<Http>>
without having to have an arc in the lazy firstInference Limitation
While
FunctionCall
andContractInstance
may be used with any type isBorrow<M>
, there are sometimes multiple choices of borrowed type that satisfy theM: Middleware
constraint.because
Middleware
has anauto_impl(&, Box, Arc)
, references, boxes, and arcs have multipleBorrow
options that satisfy the constraint.E.g.
Arc<M>
has these impls:impl Borrow<Arc<M>> for Arc<M>
impl Borrow<M> for Arc<M>
impl Borrow<&M> for Arc<M>
as a result, type inference fails when calling
ContractInstance::new(address, arc_mware)
users must instead specifyContractInstance::<_, ConcreteMiddlewareType>(address, arc_mware)
Removing the
auto_impl
will solve this. However, we don't know the extent of reliance on that auto_impl. As a result, using the backwards-compatibility aliasesContract
andContractCall
is still recommended. the Followup work section describes a migration route to fix inference and deprecate the backwards-compatibility aliasesFollowup work
ContractInternal
Contract
nameLib-wide refactoring plan:
MyMiddleware { inner: Borrow<M> }
?)type Contract = ContractInstance<Arc<M>, M>
auto_impl(&, Box, Arc)
onMiddleware
PR Checklist