Skip to content

Commit 58587e8

Browse files
bors[bot]Dirbaio
andauthored
Merge #380
380: async/spi: replace the "give back the Bus" hack with a raw pointer. r=ryankurte a=Dirbaio The async SPI trait contains a "hack" to workaround limitations in Rust's borrow checker: #347 It turns out the hack doesn't allow many shared bus implementations, for example when using an async Mutex: The `transaction` method gets `&'a mut self`, and the closure wants `&'a mut Self::Bus`. This only works if the Bus is a direct field in `self`. In the mutex case, the Bus has to come from inside the `MutexGuard`, so it'll live for less than `'a`. See https://gist.github.com/kalkyl/ad3075182d610e7b413b8bbe1228ab90 which fails with the following error: ``` error[E0597]: `bus` does not live long enough --> src/shared_[spi.rs:78](http://spi.rs:78/):34 | 63 | fn transaction<'a, R, F, Fut>(&'a mut self, f: F) -> Self::TransactionFuture<'a, R, F, Fut> | -- lifetime `'a` defined here ... 78 | let (bus, f_res) = f(&mut bus).await; | --^^^^^^^^- | | | | | borrowed value does not live long enough | argument requires that `bus` is borrowed for `'a` ... 89 | } | - `bus` dropped here while still borrowed ``` This is an alternative hack. If lifetimes don't work, simply don't use lifetimes at all! - Downside: it needs `unsafe{}` in all callers of transaction (but these should be rare, many users will use the `SpiDeviceExt` helpers. - Upside: it's now possible to write sound shared bus impls. - Upside: it no longer requires the "give back the bus" hack, it's now possible again to use `?` inside the closure for error handling. cc `@kalkyl` Co-authored-by: Dario Nieuwenhuis <[email protected]>
2 parents dd7bd7f + 8cf16a7 commit 58587e8

File tree

1 file changed

+30
-42
lines changed

1 file changed

+30
-42
lines changed

embedded-hal-async/src/spi.rs

+30-42
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,8 @@ pub trait SpiDevice: ErrorType {
2222
where
2323
Self: 'a,
2424
R: 'a,
25-
F: FnOnce(&'a mut Self::Bus) -> Fut + 'a,
26-
Fut: Future<
27-
Output = (
28-
&'a mut Self::Bus,
29-
Result<R, <Self::Bus as ErrorType>::Error>,
30-
),
31-
> + 'a;
25+
F: FnOnce(*mut Self::Bus) -> Fut + 'a,
26+
Fut: Future<Output = Result<R, <Self::Bus as ErrorType>::Error>> + 'a;
3227

3328
/// Perform a transaction against the device.
3429
///
@@ -42,15 +37,14 @@ pub trait SpiDevice: ErrorType {
4237
/// The locking mechanism is implementation-defined. The only requirement is it must prevent two
4338
/// transactions from executing concurrently against the same bus. Examples of implementations are:
4439
/// critical sections, blocking mutexes, async mutexes, returning an error or panicking if the bus is already busy.
40+
///
41+
/// The current state of the Rust typechecker doesn't allow expressing the necessary lifetime constraints, so
42+
/// the `f` closure receives a lifetime-less `*mut Bus` raw pointer instead. The pointer is guaranteed
43+
/// to be valid for the entire duration the closure is running, so dereferencing it is safe.
4544
fn transaction<'a, R, F, Fut>(&'a mut self, f: F) -> Self::TransactionFuture<'a, R, F, Fut>
4645
where
47-
F: FnOnce(&'a mut Self::Bus) -> Fut + 'a,
48-
Fut: Future<
49-
Output = (
50-
&'a mut Self::Bus,
51-
Result<R, <Self::Bus as ErrorType>::Error>,
52-
),
53-
> + 'a;
46+
F: FnOnce(*mut Self::Bus) -> Fut + 'a,
47+
Fut: Future<Output = Result<R, <Self::Bus as ErrorType>::Error>> + 'a;
5448
}
5549

5650
/// Helper methods for SpiDevice.
@@ -147,8 +141,9 @@ impl<T: SpiDevice> SpiDeviceExt for T {
147141
Word: Copy + 'static,
148142
{
149143
self.transaction(move |bus| async move {
150-
let res = bus.read(buf).await;
151-
(bus, res)
144+
// safety: `bus` is a valid pointer we're allowed to use for the duration of the closure.
145+
let bus = unsafe { &mut *bus };
146+
bus.read(buf).await
152147
})
153148
}
154149

@@ -164,8 +159,9 @@ impl<T: SpiDevice> SpiDeviceExt for T {
164159
Word: Copy + 'static,
165160
{
166161
self.transaction(move |bus| async move {
167-
let res = bus.write(buf).await;
168-
(bus, res)
162+
// safety: `bus` is a valid pointer we're allowed to use for the duration of the closure.
163+
let bus = unsafe { &mut *bus };
164+
bus.write(buf).await
169165
})
170166
}
171167

@@ -185,8 +181,9 @@ impl<T: SpiDevice> SpiDeviceExt for T {
185181
Word: Copy + 'static,
186182
{
187183
self.transaction(move |bus| async move {
188-
let res = bus.transfer(read, write).await;
189-
(bus, res)
184+
// safety: `bus` is a valid pointer we're allowed to use for the duration of the closure.
185+
let bus = unsafe { &mut *bus };
186+
bus.transfer(read, write).await
190187
})
191188
}
192189

@@ -205,8 +202,9 @@ impl<T: SpiDevice> SpiDeviceExt for T {
205202
Word: Copy + 'static,
206203
{
207204
self.transaction(move |bus| async move {
208-
let res = bus.transfer_in_place(buf).await;
209-
(bus, res)
205+
// safety: `bus` is a valid pointer we're allowed to use for the duration of the closure.
206+
let bus = unsafe { &mut *bus };
207+
bus.transfer_in_place(buf).await
210208
})
211209
}
212210
}
@@ -216,18 +214,13 @@ impl<T: SpiDevice> SpiDevice for &mut T {
216214

217215
type TransactionFuture<'a, R, F, Fut> = T::TransactionFuture<'a, R, F, Fut>
218216
where
219-
Self: 'a, R: 'a, F: FnOnce(&'a mut Self::Bus) -> Fut + 'a,
220-
Fut: Future<Output = (&'a mut Self::Bus, Result<R, <Self::Bus as ErrorType>::Error>)> + 'a;
217+
Self: 'a, R: 'a, F: FnOnce(*mut Self::Bus) -> Fut + 'a,
218+
Fut: Future<Output = Result<R, <Self::Bus as ErrorType>::Error>> + 'a;
221219

222220
fn transaction<'a, R, F, Fut>(&'a mut self, f: F) -> Self::TransactionFuture<'a, R, F, Fut>
223221
where
224-
F: FnOnce(&'a mut Self::Bus) -> Fut + 'a,
225-
Fut: Future<
226-
Output = (
227-
&'a mut Self::Bus,
228-
Result<R, <Self::Bus as ErrorType>::Error>,
229-
),
230-
> + 'a,
222+
F: FnOnce(*mut Self::Bus) -> Fut + 'a,
223+
Fut: Future<Output = Result<R, <Self::Bus as ErrorType>::Error>> + 'a,
231224
{
232225
T::transaction(self, f)
233226
}
@@ -449,27 +442,22 @@ where
449442

450443
type TransactionFuture<'a, R, F, Fut> = impl Future<Output = Result<R, Self::Error>> + 'a
451444
where
452-
Self: 'a, R: 'a, F: FnOnce(&'a mut Self::Bus) -> Fut + 'a,
453-
Fut: Future<Output = (&'a mut Self::Bus, Result<R, <Self::Bus as ErrorType>::Error>)> + 'a;
445+
Self: 'a, R: 'a, F: FnOnce(*mut Self::Bus) -> Fut + 'a,
446+
Fut: Future<Output = Result<R, <Self::Bus as ErrorType>::Error>> + 'a;
454447

455448
fn transaction<'a, R, F, Fut>(&'a mut self, f: F) -> Self::TransactionFuture<'a, R, F, Fut>
456449
where
457450
R: 'a,
458-
F: FnOnce(&'a mut Self::Bus) -> Fut + 'a,
459-
Fut: Future<
460-
Output = (
461-
&'a mut Self::Bus,
462-
Result<R, <Self::Bus as ErrorType>::Error>,
463-
),
464-
> + 'a,
451+
F: FnOnce(*mut Self::Bus) -> Fut + 'a,
452+
Fut: Future<Output = Result<R, <Self::Bus as ErrorType>::Error>> + 'a,
465453
{
466454
async move {
467455
self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?;
468456

469-
let (bus, f_res) = f(&mut self.bus).await;
457+
let f_res = f(&mut self.bus).await;
470458

471459
// On failure, it's important to still flush and deassert CS.
472-
let flush_res = bus.flush().await;
460+
let flush_res = self.bus.flush().await;
473461
let cs_res = self.cs.set_high();
474462

475463
let f_res = f_res.map_err(ExclusiveDeviceError::Spi)?;

0 commit comments

Comments
 (0)