Skip to content

Commit af08fdf

Browse files
authored
Support flat structs in FromStr derive (#469, #467)
Required for #467 Requires #468 ## Synopsis We support flat structs for `Display`: ```rust #[derive(Display)] struct Foo; ``` But `FromStr` doesn't support it. ## Solution Allow using `#[derive(FromStr)]` on structs with no fields. Similarly to enums it will use fuzzy matching. Exact matching would be possible to enable in #469 by using `#[from_str(rename_all = "...")]` attribute.
1 parent 1c2fe2a commit af08fdf

File tree

5 files changed

+408
-100
lines changed

5 files changed

+408
-100
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1414
([#443](https://github.com/JelteF/derive_more/pull/443))
1515
- Support `Option` fields for `Error::source()` in `Error` derive.
1616
([#459](https://github.com/JelteF/derive_more/pull/459))
17+
- Support structs with no fields in `FromStr` derive.
18+
([#469](https://github.com/JelteF/derive_more/pull/469))
1719

1820
### Changed
1921

impl/doc/from_str.md

Lines changed: 95 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,172 @@
11
# What `#[derive(FromStr)]` generates
22

3-
Deriving `FromStr` only works for enums with no fields
4-
or newtypes, i.e structs with only a single
5-
field. The result is that you will be able to call the `parse()` method on a
6-
string to convert it to your newtype. This only works when the type that is
7-
contained in the type implements `FromStr`.
3+
Deriving `FromStr` only works for enums/structs with no fields
4+
or newtypes (structs with only a single field). The result is
5+
that you will be able to call the `parse()` method on a string
6+
to convert it to your newtype. This only works when the wrapped
7+
type implements `FromStr` itself.
88

99

1010

1111

12-
## Example usage
13-
14-
```rust
15-
# use derive_more::FromStr;
16-
#
17-
#[derive(FromStr, Debug, Eq, PartialEq)]
18-
struct MyInt(i32);
19-
20-
#[derive(FromStr, Debug, Eq, PartialEq)]
21-
struct Point1D{
22-
x: i32,
23-
}
24-
25-
assert_eq!(MyInt(5), "5".parse().unwrap());
26-
assert_eq!(Point1D{x: 100}, "100".parse().unwrap());
27-
```
28-
12+
## Forwarding
2913

14+
Deriving forwarding implementation is only supported for newtypes
15+
(structs with only a single field).
3016

3117

32-
## Tuple structs
18+
### Tuple structs
3319

3420
When deriving `FromStr` for a tuple struct with one field:
35-
3621
```rust
3722
# use derive_more::FromStr;
3823
#
39-
#[derive(FromStr)]
24+
#[derive(FromStr, Debug, Eq, PartialEq)]
4025
struct MyInt(i32);
41-
```
4226

43-
Code like this will be generated:
27+
assert_eq!("5".parse::<MyInt>().unwrap(), MyInt(5));
28+
```
4429

30+
Code like this is generated:
4531
```rust
4632
# struct MyInt(i32);
4733
impl derive_more::core::str::FromStr for MyInt {
4834
type Err = <i32 as derive_more::core::str::FromStr>::Err;
49-
fn from_str(src: &str) -> Result<Self, <i32 as derive_more::core::str::FromStr>::Err> {
50-
return Ok(MyInt(i32::from_str(src)?));
35+
fn from_str(s: &str) -> Result<Self, Self::Err> {
36+
Ok(Self(i32::from_str(s)?))
5137
}
5238
}
5339
```
5440

5541

56-
57-
58-
## Regular structs
42+
### Regular structs
5943

6044
When deriving `FromStr` for a regular struct with one field:
61-
6245
```rust
6346
# use derive_more::FromStr;
6447
#
65-
#[derive(FromStr)]
48+
#[derive(FromStr, Debug, Eq, PartialEq)]
6649
struct Point1D {
6750
x: i32,
6851
}
69-
```
7052

71-
Code like this will be generated:
53+
assert_eq!("100".parse::<Point1D>().unwrap(), Point1D { x: 100 });
54+
```
7255

56+
Code like this is generated:
7357
```rust
7458
# struct Point1D {
7559
# x: i32,
7660
# }
7761
impl derive_more::core::str::FromStr for Point1D {
7862
type Err = <i32 as derive_more::core::str::FromStr>::Err;
79-
fn from_str(src: &str) -> Result<Self, <i32 as derive_more::core::str::FromStr>::Err> {
80-
return Ok(Point1D {
81-
x: i32::from_str(src)?,
82-
});
63+
fn from_str(s: &str) -> Result<Self, Self::Err> {
64+
Ok(Self {
65+
x: i32::from_str(s)?,
66+
})
8367
}
8468
}
8569
```
8670

8771

8872

8973

90-
## Enums
74+
## Flat representation
9175

92-
When deriving `FromStr` for an enums with variants with no fields it will
93-
generate a `from_str` method that converts strings that match the variant name
94-
to the variant. If using a case insensitive match would give a unique variant
95-
(i.e you dont have both a `MyEnum::Foo` and a `MyEnum::foo` variant) then case
96-
insensitive matching will be used, otherwise it will fall back to exact string
97-
matching.
76+
Deriving flat string representation is only supported for empty enums and
77+
structs (with no fields).
9878

99-
Since the string may not match any variants an error type is needed, so the
100-
`derive_more::FromStrError` will be used for that purpose.
10179

102-
e.g. Given the following enum:
80+
### Empty enums
81+
82+
When deriving `FromStr` for enums with empty variants, it will generate a
83+
`from_str()` method converting strings matching the variant name to the variant.
84+
If using a case-insensitive match would give a unique variant (i.e. you don't have
85+
both `MyEnum::Foo` and `MyEnum::foo` variants), then case-insensitive matching will
86+
be used, otherwise it will fall back to exact string matching.
10387

88+
Since the string may not match any variants an error type is needed, so the
89+
`derive_more::FromStrError` is used for that purpose.
90+
91+
Given the following enum:
10492
```rust
10593
# use derive_more::FromStr;
10694
#
107-
#[derive(FromStr)]
95+
#[derive(FromStr, Debug, Eq, PartialEq)]
10896
enum EnumNoFields {
10997
Foo,
11098
Bar,
11199
Baz,
100+
BaZ,
112101
}
113-
```
114102

115-
Code like this will be generated:
103+
assert_eq!("foo".parse::<EnumNoFields>().unwrap(), EnumNoFields::Foo);
104+
assert_eq!("Foo".parse::<EnumNoFields>().unwrap(), EnumNoFields::Foo);
105+
assert_eq!("FOO".parse::<EnumNoFields>().unwrap(), EnumNoFields::Foo);
106+
107+
assert_eq!("Bar".parse::<EnumNoFields>().unwrap(), EnumNoFields::Bar);
108+
assert_eq!("bar".parse::<EnumNoFields>().unwrap(), EnumNoFields::Bar);
109+
110+
assert_eq!("Baz".parse::<EnumNoFields>().unwrap(), EnumNoFields::Baz);
111+
assert_eq!("BaZ".parse::<EnumNoFields>().unwrap(), EnumNoFields::BaZ);
112+
assert_eq!(
113+
"other".parse::<EnumNoFields>().unwrap_err().to_string(),
114+
"Invalid `EnumNoFields` string representation",
115+
);
116+
```
116117

118+
Code like this is generated:
117119
```rust
118120
# enum EnumNoFields {
119121
# Foo,
120122
# Bar,
121123
# Baz,
124+
# BaZ,
122125
# }
123126
#
124127
impl derive_more::core::str::FromStr for EnumNoFields {
125128
type Err = derive_more::FromStrError;
126-
fn from_str(src: &str) -> Result<Self, derive_more::FromStrError> {
127-
Ok(match src.to_lowercase().as_str() {
128-
"foo" => EnumNoFields::Foo,
129-
"bar" => EnumNoFields::Bar,
130-
"baz" => EnumNoFields::Baz,
129+
fn from_str(s: &str) -> Result<Self, derive_more::FromStrError> {
130+
Ok(match s.to_lowercase().as_str() {
131+
"foo" => Self::Foo,
132+
"bar" => Self::Bar,
133+
"baz" if s == "Baz" => Self::Baz,
134+
"baz" if s == "BaZ" => Self::BaZ,
131135
_ => return Err(derive_more::FromStrError::new("EnumNoFields")),
132136
})
133137
}
134138
}
135139
```
140+
141+
142+
### Empty structs
143+
144+
Deriving `FromStr` for structs with no fields is similar to enums,
145+
but involves only case-insensitive matching by now.
146+
147+
Given the following struct:
148+
```rust
149+
# use derive_more::FromStr;
150+
#
151+
#[derive(FromStr, Debug, Eq, PartialEq)]
152+
struct Foo;
153+
154+
assert_eq!("foo".parse::<Foo>().unwrap(), Foo);
155+
assert_eq!("Foo".parse::<Foo>().unwrap(), Foo);
156+
assert_eq!("FOO".parse::<Foo>().unwrap(), Foo);
157+
```
158+
159+
Code like this is generated:
160+
```rust
161+
# struct Foo;
162+
#
163+
impl derive_more::core::str::FromStr for Foo {
164+
type Err = derive_more::FromStrError;
165+
fn from_str(s: &str) -> Result<Self, derive_more::FromStrError> {
166+
Ok(match s.to_lowercase().as_str() {
167+
"foo" => Self,
168+
_ => return Err(derive_more::FromStrError::new("Foo")),
169+
})
170+
}
171+
}
172+
```

0 commit comments

Comments
 (0)