Method Signatures
Below is a Java method signature and the equivalent rust signature.
void printNum(int num);
#![allow(unused_variables)] fn main() { trait Example { fn print_num(num: i32); } }
Things to notice:
The Rust method starts with the keyword “fn” this simply indicates that a function is being declared. This is followed by the method name and the parameters to the method in parentheses. Unlike Java where the type appears on the left followed by the argument name, in Rust the name comes first followed by a :
followed by the type. (Similar to how variables are declared)
If the function had a return value it would be declared on the right hand side like this:
int add(int a, int b);
#![allow(unused_variables)] fn main() { trait Example { fn add(a: i32, b: i32) -> i32; } }
There is no void
in Rust. If there isn't a return value, just don't provide one.
Just as in Java the method body is in between the curly braces.
int add(int a, int b) {
return a + b;
}
#![allow(unused_variables)] fn main() { fn add(a: i32, b: i32) -> i32 { return a + b; } }
Borrowing
In Rust a variable can also be “borrowed”. When the parameter to a method is borrowed, it means the method promises not keep the parameter after the method has returned. Or anything it obtained from the parameter.
A method that takes a borrowed parameter cannot assign the parameter to a member variable. Also it can only pass that parameter to other methods which also borrow it. (Otherwise it would be able violate its contract indirectly.)
|
To indicate a parameter is borrowed, in the method signature, place a “&” in front of the type.
As an example, in Java you might define a method isSorted()
like this
boolean isSorted(List<Integer> values);
that returns a boolean indicating if a list is sorted. In Rust you would add an ‘&’ in front of the type in the method signature to indicate that it will not retain any references to the list nor its contents when the method returns.
#![allow(unused_variables)] fn main() { trait Example { fn is_sorted(values: & Vec<i32>) -> bool; } }
It is helpful to think of ‘borrowed’ as a part of the type(i.e. “The method takes a borrowed list.”)
This declaration provides a strong and useful guarantee to the callers of a method.
Of course it would be worthless if in a newer version, the method just deleted the ‘&’ from its signature and removed the guarantee. To prevent this and to make the guarantee explicit in the caller's code, when a method that borrows a parameter is invoked, the caller puts an ‘&’ in front of the variable name being passed in. For example
#![allow(unused_variables)] fn main() { fn is_sorted(values: & Vec<i32>) -> bool { true } let values = vec![1, 2, 3]; if (is_sorted(&values)) { //... } }
This is only needed if the variable being passed isn't borrowed already. (Otherwise it would be redundant, because it can only pass it to methods which borrow it.)
Passing a parameter to a method that borrows is sometimes referred to a the parameter being “lent” to the method.
Mutability
In Java a variable can be declared final
. This means the value cannot be reassigned. It however does not guarentee that its contents won't change. For example if you have the method:
void process(final List<Foo> toProcess);
You can't tell from looking at the signature if the toProcess
list will be modified or if the individual items in the list will be modified.
Rust avoids this ambiguity with the keyword mut
. All values are unmodifyable by default, and if the variable or any of its contents are going to be changed it is prefixed with the mut
keyword for eample:
#![allow(unused_variables)] fn main() { let mut value = 0; value += 10; }
Similar to variable declaration, if you want to modify a borrowed parameter, you use the mut
keyword. This goes right in front of the type.
void populateCounts(HashMap<String, int> itemCounts);
#![allow(unused_variables)] fn main() { use std::collections::HashMap; trait Example { fn populate_counts(item_counts: &mut HashMap<String, i32>); } }
mut
can be thought of a part of the type(i.e. “a borrowed mutable Hashmap” as opposed to “a Hashmap").
When ‘Borrowed’ is combined with mut
, the &
goes first. If you wanted to write a method to sort a list, it would take a borrowed mutable list.
#![allow(unused_variables)] fn main() { trait Example { fn sort(names: &mut Vec<String>); } }
This signature means sort()
may change the list, but only during the invocation of the method and will retain no references to the list or its contents once the method returns.
Javadocs
Documentation is an area where Rust and Java are very similar. In Java, you might add a javdoc like this
/**
* Computes an approximation of {@code 1/sqrt(a)} segnifigantly faster.
* However compared to using {@link java.lang.Math.sqrt()} the result is much less accurate.
* @param a the value to compute the inverse square root of.
* @return an approximate inverse square root of the passed parameter.
*/
public double fastInvSqrt(double a);
which can be automatically transformed into HTML documentation.
Rustdocs
Rust has rustdocs which works similarly. You could write the following:
/**
* Computes an approximation of `1/a.sqrt()` segnifigantly faster.
* However compared to using [`sqrt`] the result is much less accurate.
* # Examples
* ```
* let a = 7.0_f64;
* let exact = 1.0 / a.sqrt();
* let approx = a.fast_inv_sqrt();
* assert!((approx-exact).abs() < 1e-5);
* ```
*/
Or instead of “/**” and a block comment, you can use “///” and line comments. So the following is equivalent:
/// Computes an approximation of `1/a.sqrt()` segnifigantly faster.
/// However compared to using [`sqrt`] the result is much less accurate.
/// # Examples
/// ```
/// let a = 7.0_f64;
/// let exact = 1.0 / a.sqrt();
/// let approx = a.fast_inv_sqrt();
/// assert!((approx-exact).abs() < 1e-5);
/// ```
(This helps with short comments as it doesn't require an extra line at the top and bottom.)
Javadocs have a number of common tags such as “@param” and “{@link }”. Below are some common ones and their Rust equivalents.
Type | Java | Rust |
---|---|---|
Link to method | {@link Foo#bar() } | [`Foo::bar()`] |
Link to URL | <a href="https://google.com"> google</a> | [google](https://google.com) |
Code snippit | {@code foo.bar()} | `foo.bar()` or ``` foo.bar(); ``` |
Parameter | {@param foo bla bla} | N/A Documented through examples and code snippits |
Return | @return bla bla | N/A Documented through examples and code snippits |
Examples | <pre> {@code //... } </pre> | # Examples ``` //... ``` |
See also | {@see "Bla bla"} | # See Also Bla bla |
Custom tag | Requires custom javac args | # My custom section //... |
Bulleted list | <ul> <li>one</li> <ul> <li>one point one</li> </ul> </ul> | * one ** one point one |
The difference between Javadocs and Rustdocs is that, to do formatting, in Java, you would inject HTML tags, where as in Rust you use Markdown syntax. So the following Javadoc and Rustdoc are equivalent __ vs __. As you can see this improves readability a lot.
To generate an view your docs you can run _. Which will put the documentation in _. When you publish your code to Crates.io (Rust’s main package repo, the docs will be published automatically.) There will be more on Crates.io and the Cargo command in chapter _.
Additional information for the doc can also be placed inside of the method, if for some reason that makes more sense from an organization point of view. So the following are equivalent __ and __.
Docs can also be hidden (If for example a feature is still being tested) like this __. Similarly you can use the _ annotation to mark them as being platform specific. For example __.
It is common practice in Rust to write a small example for how to use each function rather than documenting all of the input and output parameters like you would in Java. So instead of __ you might write __ in Rust. These examples aren’t just for show, they also get automatically turned into unit tests. For example __. This makes sure your documentation stays up to date with the code. If you want to hide a few lines of setup at the top of an example you can use _. For example __.
Macros
Ok, let’s finally get around to writing HelloWorld:
/// Prints "Hello world!". fn main() { println!("Hello World!"); }
You may be wondering: “What is that exclamation point doing at the end of that function name?” The answer is println!
is not a normal function but a “Macro”. Rust macros are "hygienic" so they don't have the horrifyingly dangerous properties of C and C++ macros and designed to be safe.
You can think of macros as a function that does things that functions can't normally do. In this case println!
supports string templates.
fn main() { let name = "Ferris"; println!("Hello {}!", name); }
|
|
|
|
|
Another place you'll see macros is initializing collections or places where you might find “varargs" in Java. For example you can initialize a Vec
with the vec!
macro:
#![allow(unused_variables)] fn main() { let number = vec![1, 2, 3, 4, 5]; }