Things I learned when writing lib with Rust
Last week, I had to write a wrapper to the Facebook Accountkit api with Rust , this is the first time I wrote a lib so I chose this simple one with only 4 endpoints, 3 GET and 1 DELETE via url, there are not many require, so pretty Simple for anyone to practice writing lib like me. Thought is simple, but when the gas works, there are many problems, not as simple as other languages javascript , php , ruby , …
Every task is simple until you start doing it. Even when writing articles
Select & str or String
If other languages you can arbitrarily type String into, but in Rust will distinguish & str is a string slices (fixed wool) and String a UTF-8 encoded, growable string, you can append a str or a char on any String , and with fixed len str do, you can't append it, it is more widely known that if you type String when you append to String , there will be realloccation String , it doesn't sound like it good
The string that gives us a function is with_capacity
1 2 | let will_append = String :: with_capacity (20); will_append.push_str ("hello"); |
With the with_capacity function , there is no reallocation process until the capacity that exceeds the value we declare here is 20 .
So choose what type to use now & str or String , which depends on your purpose, if you don't want the inheritance of params to be used, then type & str , if you want to be owner of params, you should use String type. however, it would be better if we knew what String 's string we used to use with with_capacity would be much better.
So I don't want to use anything like that, I don't want to use it all, there's a reason.
Functions can receive multiple data types
This helps our function become more generic, also partly limiting the error that occurs if the wrong type of transmission. It's a little confusing to get an example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | pub struct AccountKit <'a> { auth: & 'a str, appsecret_proof: Option <& 'a str>, } impl <'a> AccountKit <' a> { pub fn with_token <S: Into <Option <& 'a str >>> (access_token: &' a str, appsecret_proof: S) -> AccountKit <'a> { AccountKit { auth: access_token, appsecret_proof: appsecret_proof.into (), } } } |
I declare appsecret_proof property is Option <str> , consider the impl case of AccountKit instead of selecting generic type S, here I fix a bit
1 2 3 4 5 6 7 8 9 10 | impl <'a> AccountKit <' a> { pub fn with_token (access_token: & 'a str, appsecret_proof: Option <& 'a str>) -> AccountKit <'a> { AccountKit { auth: access_token, appsecret_proof: appsecret_proof, } } } |
If the above case, we can only pass into Option , if passed in type & str, then fail right, the function to come here can be fine. However, when we expand, we want to transmit type & str or Option , the non- generic function does not meet our requirements.
Going back to the generic function above, why is it so easy to do so since simply the 1.12 rust version has a small feature.
Option implements From for its contained type
We can convert from type & str to Option <& str> if there is one
implement from & str for Option <& str> , then check in trait From to see if it is available? Very lucky to implement that
1 | impl <'a> From <&' a str> for Cow <'a, str> |
So from version 1.12 , when the function of hcungs is more flexible than the input type. For example
Error handling
Usually we can handle errors with if and else , or even use unwrap . If you use unwrap , it is only suitable for the code you want to quick test, and production should not be used because it has panic to crash the running program, and if , else if the code is long, it will make us uncontrollable enough.
Another way is to use try macros ! , when you try! then it is imperative that we declare the error type returned, because we try it ourselves ! will return to the error and be caught in our previous error declaration. And try! used with functions whose return type is Result .
Like implement below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | pub trait DoRequest: BaseRequest { fn retriev_json (& self) -> Result <AccountKitResponse, AccountKitError> { let url = try! (Url :: parse (self.url ())); let fresh_net = try! (request :: Request :: new (self.method (), url)); let streaming_req = try! (fresh_net.start ()); let mut response = thử! (streaming_req.send ()); let mut s = String :: new (); let status = response.status; let status_code = status.to_u16 (); let reason = status.canonical_reason (); try! (response.read_to_string (& mut s)); Ok (AccountKitResponse { body: s, status_code: status_code, canonical_reason: reason, }) } } |
Ignore other things to pay attention to AccountKitError , this is an enum that gathers all the errors we define ourselves.
1 2 3 4 5 | pub enum AccountKitError { HttpError (error :: Error), UrlError (ParseError), IoError (io :: Error), } |
Like declaring anything, declare here, in this case they manually redefine the error in our lib but based on the error was processed in third party.
When using try! then it will automatically return to the correct case of our error like the try! example (Url :: parse (self.url ())) it will return enum AccountKitError :: UrlError (ParseError) to see the error details are What we need implements 2 trait fmt :: Display and convert :: From
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | impl From <error :: Error> for AccountKitError { fn from (err: error :: Error) -> AccountKitError { AccountKitError :: HttpError (err) } } impl From <ParseError> for AccountKitError { fn from (err: ParseError) -> AccountKitError { AccountKitError :: UrlError (err) } } impl From <io :: Error> for AccountKitError { fn from (err: io :: Error) -> AccountKitError { AccountKitError :: IoError (err) } } impl fmt :: Display for AccountKitError { fn fmt (& self, f: & mut fmt :: Formatter) -> fmt :: Result { match * self { AccountKitError :: HttpError (ref err) => write! (F, "{}", err), AccountKitError :: UrlError (ref err) => write! (F, "{}", err), AccountKitError :: IoError (ref err) => write! (F, "{}", err), } } } |
If there's another case of error, we just need to declare what kind of error we have.
Also with the above case we can code in style
1 2 3 4 | let url = match Url :: parse (self.url ())) { Ok (url) => url, Err (err) => return Err (AccountKit :: UrlError (e)) }; |
However when compared to this code with the use of try! everyone chose try! because it is short and flexible in returning Error
Summary
Code Rust is quite interesting, has to think a lot and make a lot of choices, but when you have the code you will love it. Also you can try this rust-accountkit lib and if you contribute it it would be great.