Problems with program compilation and execution will often result in major (5-10 point) deductions during grading, though for less severe concerns (e.g., a compiler warning that should have been fixed but does not indicate a deeper problem with the program), minor (1-3 point) deductions are possible.
main
function must return a value
of 0 on successful termination, and should return a unique non-zero value for
each different kind of unrecoverable failure otherwise: please avoid using the
same non-zero return value or the same usage or error message for
different kinds of errors (this often results from "copy and paste" coding).
When different return values are possible, it's also good practice to document in
the code and in your readme file what each return value means through
(1) giving each non-zero return value a descriptively named label (see the
guidelines below about not using hard coded numbers); (2) providing comments in the code; and (3) describing the kind of error that occurred (and how to avoid
it) in the usage message(s) and error message(s) the program generates.
std::process::exit()
and instead use a return statement that propagates an appropriate error
(e.g., using a Result
type or an ExitCode
type).
Using std::process::exit()
to end the program
in the middle of a function interferes with the normal
graceful shutdown of the program, and the release of resources the program
acquired during its execution. It's better to use return statements with
appropriate values (including using the Result
type to distinguish
correct results from erroneous ones, though if so give comments why)
to cause the program to unwind its call stack and return an appropriate value
(indicating success or failure) from its main
function.
One very helpful question to ask throughout the debugging
process is "what was the last point where my program is known to
be working correctly, and what was the first point where it
is known to be working incorrectly?" A debugger like rust-gdb
(which is installed with rustc
in the Linux Lab machines)
is an invaluable tool in partially automating this process.
Problems with program style and documentation usually result in only minor deductions during grading unless they contribute to a larger issue with program correctness, but even minor deductions can add up quickly if there are a lot of them. In the longer term, practicing a consistent and high quality programming style will make your code more accessible to others (and to you after you've been away working on other code for a while), which is essential in modern team programming environments.
unsafe
to get code to compile, and
instead address the issue that is causing any compiler error or warning. If
code must be designated unsafe (which shouldn't be necessary in this course
except in a studio exercise that explicitly says to do so for purposes of
exploration) then you must provide comments explaining why that is the case.
#[cfg]
attribute) to compile
them in/out (by default they should be compiled out) and comment
why leaving them in the code is appropriate (e.g., if you intend
to use them for debugging purposes in a subsequent lab assignment).
2
and instead
use an appropriately typed and descriptively named constant, like the following:
const MINIMUM_ARGS: usize = 2;
#[allow(dead_code)]
to suppress warnings about members of a struct
not being read when relying heavily on derived implrementations (e.g., via
the #[derive]
) attribute, design so that all members of a stuct
are used directly by the code you provide in its implementation or by the code
that uses the struct. In selected cases where suppressing a warning
is the most reasonable thing to do (e.g., if you cannot guarantee code that
uses a struct will necessarily access all its members, and relying on a
derived implementation is appropriate), you must provide comments in the
code and in your accompanying documentation (i.e., your readme file) explaining
why it was appropriate to suppress that warning.
match
to destructure it to
obtain one or more descriptively named references from it. For example, rather
than writing:
if current_part != t.1 { println!(""); println!("{}.", t.1); current_part = t.1.clone(); }
instead say:
match t { // destructure the tuple to obtain its part name (.., part_name, ..) => { if current_part != *part_name { println!(""); println!("{}.", part_name); current_part = part_name.clone(); } } }
loop
expression for a non-terminating iteration (or one that
should be exited via a break
or return
statement),
instead of using e.g., while (true)
. Similarly, put the most
salient case in the if
branch and the "otherwise" case in the
else
branch, rather than negating the conditions to order them the
other way. Note that in some cases, Rust will issue a warning and
will suggest the better alternative, at compile time.