-
Notifications
You must be signed in to change notification settings - Fork 1
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
Use of try_into
removes any compile-time benefit over runtime validation libs
#3
Comments
so this is no different to eg using the |
PS Here's the full small test: use bioimg_spec::rdf::{Rdf, Version, author::Author, badge::Badge, cite_entry::CiteEntry, SpdxLicense};
use url::Url;
fn main() {
println!("I'm a Rust coder hard coding an RDF...");
let bad_rdf = Rdf {
format_version: Version {
major: 1,
minor: 2,
patch: 3,
},
description: "".try_into().unwrap(),
name: "".try_into().unwrap(),
attachments: None,
authors: Some(vec![Author {
name: "John Doe".try_into().unwrap(),
affiliation: "Some University".try_into().unwrap(),
email: "john.doe@some_university.com".try_into().unwrap(),
github_user: "john_doe".try_into().unwrap(),
orcid: "0000-0002-8205-121X".to_owned().try_into().unwrap(), //FIXME
}]),
badges: Some(vec![Badge {
label: "x".try_into().unwrap(),
icon: Url::parse("http://some.icon/bla").unwrap().into(),
url: Url::parse("http://some.url/to/icon").unwrap().into(),
}]),
cite: Some(vec![CiteEntry {
text: "Plz cite eme".try_into().unwrap(),
doi: "blabla".try_into().unwrap(),
url: Url::parse("https://blas/bla").unwrap(),
}]),
covers: None,
documentation: Some(Url::parse("http://example.com/docs").unwrap().into()),
download_url: Some(Url::parse("http://blas.blus/blis").unwrap().into()),
git_repo: Some(Url::parse("https://github.com/blas/blus").unwrap()),
icon: Some("x".try_into().unwrap()),
id: Some("some_id_goes_here".try_into().unwrap()),
license: Some(SpdxLicense::Adobe_Utopia),
links: Some(vec![]),
maintainers: Some(vec![]),
rdf_source: None,
source: None,
tags: None,
version: Some(Version {
major: 4,
minor: 5,
patch: 6,
}),
};
println!("{:?}", bad_rdf);
} |
It's not Using It's fine to use |
Hm no it is the try_into I think - because that can resolve to a result or an err - both are technically fine as far as the compiler is concerned, to allow for error propagation etc. As far as I can see, the only way around this is if I could do something like: ...
description: BoundedString::<1, 1023> (...)
... ...but that defeats the object I think! |
so at the end of the day, there's no benefit compared with |
If you can write code with your current lib that causes a compiler error because of entering eg a 0-length description please share - I probably missed something then. |
They are not both technically fine when assigning to
it's causing a runtime error, not a compiler error. And it is doing so because of the
|
I'm not saying that you can just remove But the point is still valid that this is all runtime, unless I misunderstood - please let me know if that's not the case - and show me an example. Or we can keep quibbling about exactly what's going on with the above... 🫣 |
We might be talking past each other on the meaning of "runtime" 😛 Sure, all checks are done in runtime; There is no way they could be done in compile-time, since the input is not available during compile-time. What I mean is that if we go the On the other hand, if we make it so that validation returns a In this second approach, the only way you could possibly get a hold of a Here's an example using the pub struct User{
#[validate(minimum = 0)]
#[validate(maximum = 120)] // users can be at most 120 years old
age: u8,
}
fn save_user_to_disk(user: User){
// is `user` valid? Should I validate again? is the caller of this function expecting me to validate it?
}
fn i_do_not_validate(){
save_user_to_disk(User{age: 200})
}
fn i_validate(){
let user = User{age: 200});
if user.validate(){
...
}
save_user_to_disk(User{age: 200})
} And a struct RawUser{
age: u8,
}
mod user{
struct User{
age: u8
}
impl TryFrom<RawUser> for User{
type Error = String
fn try_from(raw: RawUser) -> Self{
if raw.age > 120{
return Err("Too old".into())
}
Ok(Self{age: raw.age})
}
}
}
fn save_user_to_disk(user: User){
// now here I'm absolutely sure that User is valid;
// there is no way the program could have executed up to this point with an invalid User
}
fn i_do_not_validate(){
//this is now a compiler error.
// now we can only get a user via try_from
save_user_to_disk(User{age: 200})
}
fn use_a_user() -> Result<...>{
let user = User::try_from(RawUser{age: 200})?; //must handle/propagate error here
save_user_to_disk(user) //this line can only be reached if user is valid
} |
We could get the same effect with a But yes, I can see that we can't just instantiate |
... Also in your That way we still use all the stuff from the I think this or the |
Yes =) That's what I was talking about here Whether
True, but the problem runs deeper, and recursively so. Going back to out struct User{
email: String,
} And say that we do the So the thing I'm getting at here is that if validation is also what instantiates, then the compiler can keep track of validation. If validation only checks a raw value, then the compiler can't help us; we have to track the validity of those fields ourselves. And it seems to me that these validation crates do the latter rather than the former. And there is also the problem of cross-field validation, which would probably still need to happen inside the |
👍 In that case let's close / mark as wontfix for now, and we can always reference this or reopen if it gets reconsidered, |
Eg:
As in your test, modified with bad string values... But this passes the compiler fine as it's the
unwrap
that will fail at runtime.Eg a test program using this gives:
The text was updated successfully, but these errors were encountered: