Skip to content

Commit 320e57d

Browse files
committed
Support generic structs in ethercat-wire-derive
By using generics for e.g. measurement values, users can separate their network logic which touch ethercrab and the fieldbus from their control logic which will be concerned with how the value should be gained, scaled, and otherwise manipulated.
1 parent c17408b commit 320e57d

File tree

6 files changed

+139
-6
lines changed

6 files changed

+139
-6
lines changed

ethercrab-wire-derive/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Derives for `ethercrab`.
1010

1111
- **(breaking)** [#230](https://github.com/ethercrab-rs/ethercrab/pull/230) Increase MSRV from 1.77
1212
to 1.79.
13+
- Support generic parameters for structs.
1314

1415
## [0.2.0] - 2024-07-28
1516

ethercrab-wire-derive/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ syn = { version = "2.0.44", features = ["full"] }
2727
trybuild = "1.0.86"
2828
ethercrab-wire = { path = "../ethercrab-wire" }
2929
syn = { version = "2.0.44", features = ["full", "extra-traits"] }
30+
pretty_assertions = "1.4.1"
3031

3132
[[bench]]
3233
name = "derive-struct"

ethercrab-wire-derive/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,39 @@ struct Middle {
156156
}
157157
```
158158

159+
## Generic structs
160+
161+
Structs can take generic parameters so long as those parameters are `EtherCrabWireRead/Write` as relevant.
162+
163+
```rust
164+
/// Status word for Beckhoff EL31xx devices and others.
165+
#[derive(ethercrab_wire::EtherCrabWireRead)]
166+
#[wire(bytes = 4)]
167+
pub struct AnalogInput<Value>
168+
where
169+
Value: ethercrab_wire::EtherCrabWireRead,
170+
{
171+
#[wire(bits = 1)]
172+
underrange: bool,
173+
#[wire(bits = 1)]
174+
overrange: bool,
175+
#[wire(bits = 2)]
176+
limit1: u8,
177+
#[wire(bits = 2)]
178+
limit2: u8,
179+
#[wire(bits = 1)]
180+
error: bool,
181+
#[wire(pre_skip = 6, bits = 1)]
182+
sync_error: bool,
183+
#[wire(bits = 1)]
184+
tx_pdo_bad: bool,
185+
#[wire(bits = 1)]
186+
tx_pdo_toggle: bool,
187+
#[wire(bits = 16)]
188+
value: Value, // <-- generic field
189+
}
190+
```
191+
159192
[`ethercrab`]: https://docs.rs/ethercrab
160193
[`ethercrab-wire`]: https://docs.rs/ethercrab-wire
161194

ethercrab-wire-derive/src/generate_struct.rs

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,10 @@ pub fn generate_struct_write(parsed: &StructMeta, input: &DeriveInput) -> proc_m
5353
}
5454
});
5555

56+
let (impl_generics, type_generics, where_clause) = parsed.generics.split_for_impl();
5657
quote! {
57-
impl ::ethercrab_wire::EtherCrabWireWrite for #name {
58+
impl #impl_generics ::ethercrab_wire::EtherCrabWireWrite for #name #type_generics
59+
#where_clause {
5860
fn pack_to_slice_unchecked<'buf>(&self, buf: &'buf mut [u8]) -> &'buf [u8] {
5961
let buf = match buf.get_mut(0..#size_bytes) {
6062
Some(buf) => buf,
@@ -75,7 +77,8 @@ pub fn generate_struct_write(parsed: &StructMeta, input: &DeriveInput) -> proc_m
7577
}
7678
}
7779

78-
impl ::ethercrab_wire::EtherCrabWireWriteSized for #name {
80+
impl #impl_generics ::ethercrab_wire::EtherCrabWireWriteSized for #name #type_generics
81+
#where_clause {
7982
fn pack(&self) -> Self::Buffer {
8083
let mut buf = [0u8; #size_bytes];
8184

@@ -144,8 +147,10 @@ pub fn generate_struct_read(parsed: &StructMeta, input: &DeriveInput) -> proc_ma
144147
}
145148
});
146149

150+
let (impl_generics, type_generics, where_clause) = parsed.generics.split_for_impl();
147151
quote! {
148-
impl ::ethercrab_wire::EtherCrabWireRead for #name {
152+
impl #impl_generics ::ethercrab_wire::EtherCrabWireRead for #name #type_generics
153+
#where_clause {
149154
fn unpack_from_slice(buf: &[u8]) -> Result<Self, ::ethercrab_wire::WireError> {
150155
let buf = buf.get(0..#size_bytes).ok_or(::ethercrab_wire::WireError::ReadBufferTooShort)?;
151156

@@ -161,8 +166,10 @@ pub fn generate_sized_impl(parsed: &StructMeta, input: &DeriveInput) -> proc_mac
161166
let name = input.ident.clone();
162167
let size_bytes = parsed.width_bits.div_ceil(8);
163168

169+
let (impl_generics, type_generics, where_clause) = parsed.generics.split_for_impl();
164170
quote! {
165-
impl ::ethercrab_wire::EtherCrabWireSized for #name {
171+
impl #impl_generics ::ethercrab_wire::EtherCrabWireSized for #name #type_generics
172+
#where_clause {
166173
const PACKED_LEN: usize = #size_bytes;
167174

168175
type Buffer = [u8; #size_bytes];
@@ -173,3 +180,54 @@ pub fn generate_sized_impl(parsed: &StructMeta, input: &DeriveInput) -> proc_mac
173180
}
174181
}
175182
}
183+
184+
#[cfg(test)]
185+
mod tests {
186+
use pretty_assertions::assert_eq;
187+
188+
use ethercrab_wire::{EtherCrabWireRead, EtherCrabWireReadWrite, EtherCrabWireWrite};
189+
190+
#[test]
191+
fn generic_struct() {
192+
#[derive(EtherCrabWireReadWrite, PartialEq, Debug)]
193+
#[wire(bytes = 8)]
194+
struct TestTypeGeneric<T: EtherCrabWireReadWrite> {
195+
#[wire(bits = 32)]
196+
a: i32,
197+
#[wire(bits = 32)]
198+
b: T,
199+
}
200+
let test_type_generic = TestTypeGeneric::<u32> {
201+
a: -16,
202+
b: u32::MAX,
203+
};
204+
let mut slice = [0u8; 8];
205+
test_type_generic.pack_to_slice(&mut slice).unwrap();
206+
assert_eq!(
207+
Ok(test_type_generic),
208+
TestTypeGeneric::<u32>::unpack_from_slice(&slice)
209+
);
210+
211+
#[derive(EtherCrabWireReadWrite, PartialEq, Debug)]
212+
#[wire(bytes = 8)]
213+
struct TestWhereClause<T>
214+
where
215+
T: EtherCrabWireReadWrite,
216+
{
217+
#[wire(bits = 32)]
218+
a: i32,
219+
#[wire(bits = 32)]
220+
b: T,
221+
}
222+
let test_where_clause = TestWhereClause::<u32> {
223+
a: -16,
224+
b: u32::MAX,
225+
};
226+
let mut slice = [0u8; 8];
227+
test_where_clause.pack_to_slice(&mut slice).unwrap();
228+
assert_eq!(
229+
Ok(test_where_clause),
230+
TestWhereClause::<u32>::unpack_from_slice(&slice)
231+
);
232+
}
233+
}

ethercrab-wire-derive/src/lib.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,39 @@
154154
//! }
155155
//! ```
156156
//!
157+
//! ## Generic structs
158+
//!
159+
//! Structs can take generic parameters so long as those parameters are `EtherCrabWireRead/Write` as relevant.
160+
//!
161+
//! ```rust
162+
//! /// Status word for Beckhoff EL31xx devices and others.
163+
//! #[derive(ethercrab_wire::EtherCrabWireRead)]
164+
//! #[wire(bytes = 4)]
165+
//! pub struct AnalogInput<Value>
166+
//! where
167+
//! Value: ethercrab_wire::EtherCrabWireRead,
168+
//! {
169+
//! #[wire(bits = 1)]
170+
//! underrange: bool,
171+
//! #[wire(bits = 1)]
172+
//! overrange: bool,
173+
//! #[wire(bits = 2)]
174+
//! limit1: u8,
175+
//! #[wire(bits = 2)]
176+
//! limit2: u8,
177+
//! #[wire(bits = 1)]
178+
//! error: bool,
179+
//! #[wire(pre_skip = 6, bits = 1)]
180+
//! sync_error: bool,
181+
//! #[wire(bits = 1)]
182+
//! tx_pdo_bad: bool,
183+
//! #[wire(bits = 1)]
184+
//! tx_pdo_toggle: bool,
185+
//! #[wire(bits = 16)]
186+
//! value: Value, // <-- generic field
187+
//! }
188+
//! ```
189+
//!
157190
//! [`ethercrab`]: https://docs.rs/ethercrab
158191
//! [`ethercrab-wire`]: https://docs.rs/ethercrab-wire
159192

ethercrab-wire-derive/src/parse_struct.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use crate::help::{all_valid_attrs, attr_exists, bit_width_attr, usize_attr};
22
use std::ops::Range;
3-
use syn::{DataStruct, DeriveInput, Fields, FieldsNamed, Ident, Type, Visibility};
3+
use syn::{DataStruct, DeriveInput, Fields, FieldsNamed, Generics, Ident, Type, Visibility};
44

55
#[derive(Clone)]
66
pub struct StructMeta {
77
/// Width in bits on the wire.
88
pub width_bits: usize,
99

1010
pub fields: Vec<FieldMeta>,
11+
pub generics: Generics,
1112
}
1213

1314
#[derive(Clone)]
@@ -42,7 +43,12 @@ pub struct FieldMeta {
4243

4344
pub fn parse_struct(
4445
s: DataStruct,
45-
DeriveInput { attrs, ident, .. }: DeriveInput,
46+
DeriveInput {
47+
attrs,
48+
ident,
49+
generics,
50+
..
51+
}: DeriveInput,
4652
) -> syn::Result<StructMeta> {
4753
// --- Struct attributes
4854

@@ -184,5 +190,6 @@ pub fn parse_struct(
184190
Ok(StructMeta {
185191
width_bits: width,
186192
fields: field_meta,
193+
generics,
187194
})
188195
}

0 commit comments

Comments
 (0)