(a rather free translation of a huge emotional article that in practice bridges the possibilities of C and Rust in terms of solving business problems and resolving bugs related to manual memory management. It should also be useful for people with experience in garbage collection - there are many differences in terms of semantics less than it might seem - approx. per. )
From the moment I became interested in Rust, it seemed like an eternity. Nevertheless, I clearly remember getting to know the borrowing analyzer ( borrow checker , hereinafter referred to as BC - approx. Per. ), Accompanied by a headache and despair. Of course I'm not the only one suffering - there are plenty of articles on the Internet on the topic of communication with warheads. However, I would like to stand out and highlight in this article the warhead from the point of view of practical benefits , and not just a headache generator.
From time to time, I come across opinions that in Rust - manual memory management ( probably since itβs not automatic with GC, then what else? - approx. Per. ), But I donβt share this point of view at all. The method used in Rust, I call the term " declarative memory management ". Why so - now I will show.
Instead of theorizing, let's write something useful.
Meet Overbook - Fiction Publishing House!
Like any publisher, Overbook has design rules. More precisely, there is only one rule, simple as doors - no commas . Overbuk sincerely believes that commas are a consequence of copyright laziness and - quote - "must be destroyed as a phenomenon." For example, the phrase "She read and laughed" - good, suitable. "She read, and then laughed," - requires correction.
It would seem simpler nowhere, however, authors regularly catch on Overbuk on pathological non-compliance with this rule! As if such a rule does not exist at all, outrageous! We have to double-check everything. Manually. Moreover, if a draft is requested by the publisher for editing, the author can send a version that has been fixed in one, but corrupted in another place, and therefore everything has to be double-checked from the very beginning. Business does not tolerate such a negligent attitude to working time, and the need arose to automate the process of catching "author's laziness" by itself. For example, a computer program. Yes, yes?
Robin is one of the employees of the publishing house who volunteered to help with writing the program, because he knew programming - this is good luck! True, everyone at the university, including Robin, was taught C and Java, and the Java publishing house was forbidden to install Java VM for no reason - that's the turn! Well, let it be C, a lot of things have been written in C, the language is sure and verified. Everything will go as it should, infa 100%.
#include <stdio.h>
int main() {
printf(".");
return 0;
}
. , Makefile:
.PHONY: all all: gcc -Wall -O0 -g main.c -o main ./main
:
:
$ make gcc -Wall -O0 -g main.c -o main ./main .
. " !"
// : #include directives
struct Mistake {
// - :
char *message;
};
struct CheckResult {
//
char *path;
// NULL
//
struct Mistake *mistake;
};
struct CheckResult check(char *path, char *buf) {
struct CheckResult result;
result.path = path;
result.mistake = NULL;
// TODO(Robin):
// TODO(Robin):
return result;
}
// : main()
" " β β " , 256 ".
#define BUF_SIZE 256 * 1024
int main() {
char buf[BUF_SIZE];
struct CheckResult result = check("sample.txt", buf);
if (result.mistake != NULL) {
printf(" !");
return 1;
}
return 0;
}
struct CheckResult check(char *path, char *buf) {
struct CheckResult result;
result.path = path;
result.mistake = NULL;
FILE *f = fopen(path, "r");
size_t len = fread(buf, 1, BUF_SIZE - 1, f);
fclose(f);
// - , , .
buf[len] = '\0';
// TODO(Robin):
return result;
}
, TODO β ? . :
// malloc
#include <stdlib.h>
// : structs, etc.
struct CheckResult check(char *path, char *buf) {
struct CheckResult result;
result.path = path;
result.mistake = NULL;
FILE *f = fopen("sample.txt", "r");
size_t len = fread(buf, 1, BUF_SIZE - 1, f);
fclose(f);
buf[len] = '\0';
// C99,
// , , ,
// .
for (size_t i = 0; i < len; i++) {
if (buf[i] == ',') {
struct Mistake *m = malloc(sizeof(Mistake));
m->message = " !";
result.mistake = m;
break;
}
}
return result;
}
"" ( β ..) sample.txt
:
', , !
:
$ make gcc -Wall -O0 -g main.c -o main ./main !
sample2.txt
β :
.
:
$ make gcc -Wall -O0 -g main.c -o main ./main
! .
:
, , . , , .
, , . ?
. ". Mistake , , .". β :
struct Mistake {
char *message;
//
char *location;
}
// ...
struct CheckResult check(char *path, char *buf) {
// : 'result'
// :
for (size_t i = 0; i < len; i++) {
if (buf[i] == ',') {
struct Mistake *m = malloc(sizeof(struct Mistake));
// :
m->location = &buf[i];
m->message = " !";
result.mistake = m;
break;
}
}
return result;
}
:
void report(struct CheckResult result) {
printf("\n");
printf("~ %s ~\n", result.path);
if (result.mistake == NULL) {
printf(" !!\n");
} else {
// : "%s" .
// 12 , ,
printf(
": %s: '%.12s'\n",
result.mistake->message,
result.mistake->location
);
}
}
int main() {
char buf[BUF_SIZE];
{
struct CheckResult result = check("sample.txt", buf);
report(result);
}
{
struct CheckResult result = check("sample2.txt", buf);
report(result);
}
}
«», β . :
$ make
gcc -Wall -O0 -g main.c -o main
./main
~ sample.txt ~
: !: ', '
~ sample2.txt ~
!!
. , , ?
#define MAX_RESULTS 128
// : ,
int main() {
char buf[BUF_SIZE];
struct CheckResult bad_results[MAX_RESULTS];
int num_results = 0;
char *paths[] = { "sample2.txt", "sample.txt", NULL };
for (int i = 0; paths[i] != NULL; i++) {
char *path = paths[i];
struct CheckResult result = check(path, buf);
bad_results[num_results++] = result;
}
for (int i = 0; i < num_results; i++) {
report(bad_results[i]);
}
}
. β , . .
$ make
gcc -Wall -O0 -g main.c -o main
./main
~ sample2.txt ~
!!
~ sample.txt ~
: !: ', '
:
, ,
, , , ! - β . ?
"!" β β " !"
. , , :
int main() {
// :
// "sample2.txt", "sample.txt"
char *paths[] = { "sample.txt", "sample2.txt", NULL };
for (int i = 0; paths[i] != NULL; i++) {
//
}
for (int i = 0; i < num_results; i++) {
report(results[i]);
}
}
```bash
$ make
gcc -Wall -O0 -g main.c -o main
./main
~ sample.txt ~
: !: ' '
~ sample2.txt ~
!!
- . , , :
β , . ! , - β !
- , , , , , :
β , . . .
. β . β .
" -?" β .
// , memcpy
#include <string.h>
struct CheckResult check(char *path, char *buf) {
// : ,
for (size_t i = 0; i < len; i++) {
if (buf[i] == ',') {
struct Mistake *m = malloc(sizeof(struct Mistake));
// 12 "m->location"
size_t location_len = len - i;
if (location_len > 12) {
location_len = 12;
}
m->location = malloc(location_len + 1);
memcpy(m->location, &buf[i], location_len);
m->location[location_len] = '\0';
m->message = " !";
result.mistake = m;
break;
}
}
return result;
}
, , , sample3.txt
:
,
, !
$ make
gcc -Wall -O0 -g main.c -o main
./main
~ sample.txt ~
: !: ', '
~ sample2.txt ~
!!
~ sample3.txt ~
mistake: : ', '
, β :
, . ,
. , , . , , , , ?
" ", . , , malloc()
, free()
. , ? , 100%, - , - - . , .
int main() {
char buf[BUF_SIZE];
struct CheckResult results[MAX_RESULTS];
int num_results = 0;
// : ,
// !
for (int i = 0; i < num_results; i++) {
free(results[i].mistake);
}
}
. β buf
results
. , .
β , ?
β , ?
β , . .
β , , ! . , . .
β . ?
. . , . , . :
β . free()
, , .
, , - , , , " ", / , .
int main() {
// ...
// !
for (int i = 0; i < num_results; i++) {
free(results[i].mistake->location);
free(results[i].mistake);
}
}
β , . free()
, , , , .
- , , . . -, , , , . :
β , free()
. .
, , , , , , . . . .
β -, , , , .
. , , , . , . , , .
, .
β , . , .
, . . - . , ?
β , , ?
β . , .
. ( !) . , , . .
. , . , . .
β , β , β ?
β , 130 .
, .
#define MAX_RESULTS 128
int main() {
char buf[BUF_SIZE];
struct CheckResult results[MAX_RESULTS];
// ...
}
, , , . results
( , ). , results
results
. buf
, results
, , buf
, . . MAX_RESULTS
256. .
β β¦ . . , . , - , , . . .
. 256 , , . . .
" ." β , β " Rust."
cargo new checker
src/main.rs
. . :
use std::fs::read_to_string;
fn main() {
let s = read_to_string("sample.txt");
}
", Ruby!" β , - . . , , Ruby. .
", ?"
fn main() {
let s = read_to_string("sample.txt");
s.find(",");
}
, :
error[E0599]: no method named `find` found for type `std::result::Result<std::string::String, std::io::Error>` in the current scope --> src\main.rs:5:7 | 5 | s.find(","); | ^^^^
. β Result<String, Error>
find()
, String
. .
fn main() -> Result<(), std::io::Error> {
let s = read_to_string("sample.txt")?;
s.find(",");
Ok(())
}
, main()
, read_to_string()
main()
, . , , . , find()
:
fn main() -> Result<(), std::io::Error> {
let s = read_to_string("sample.txt")?;
let result = s.find(",");
println!("Result: {:?}", result);
Ok(())
}
$ cargo run --quiet Result: Some(22)
. - , Option::Some(index)
, , Option::None
. ( , β ..):
fn main() -> Result<(), std::io::Error> {
let path = "sample.txt";
let s = read_to_string(path)?;
println!("~ {} ~", path);
match s.find(",") {
Some(index) => {
println!(": : β{}", index);
},
None => println!(" !!"),
}
Ok(())
}
$ cargo run --quiet ~ sample.txt ~ : : β22
, , , . .
fn main() -> Result<(), std::io::Error> {
let path = "sample.txt";
let s = read_to_string(path)?;
println!("~ {} ~", path);
match s.find(",") {
Some(index) => {
// , ,
// 12 ,
//
let slice = &s[index..];
println!(": : {:?}", slice);
}
None => println!(" !!"),
}
Ok(())
}
$ cargo run --quiet
~ sample.txt ~
: : ', , !'
. malloc()
! memcpy()
! ( Go , β ..)! {:?}
, , , . , free()
. , , β s
match
. (&s[index..]
) , , . , , .
main()
, , check()
:
fn main() -> Result<(), std::io::Error> {
check("sample.txt")?;
Ok(())
}
fn check(path: &str) -> Result<(), std::io::Error> {
let s = read_to_string(path)?;
println!("~ {} ~", path);
match s.find(",") {
Some(index) => {
let slice = &s[index..];
println!(": : {:?}", slice);
}
None => println!(" !!"),
}
Ok(())
}
, . .
, , . :
fn main() -> Result<(), std::io::Error> {
let mut s = String::with_capacity(256 * 1024);
check("sample.txt", &mut s)?;
Ok(())
}
fn check(path: &str, s: &mut String) -> Result<(), std::io::Error> {
use std::fs::File;
use std::io::Read;
s.clear();
File::open(path)?.read_to_string(s)?;
println!("~ {} ~", path);
match s.find(",") {
Some(index) => {
let slice = &s[index..];
println!(": : {:?}", slice);
}
None => println!(" !!"),
}
Ok(())
}
:
s
#include
use
Read
read_to_string
check()
, , . check()
CheckResult
, Mistake
. Rust CheckResult
, Option<Mistake>
. , Mistake
. :
struct Mistake {
location: &str,
}
. .
$ cargo run --quiet error[E0106]: missing lifetime specifier --> src\main.rs:10:15 | 10 | location: &str, | ^ expected lifetime parameter
"", , " !", , . , , β ( β β ..). , :
struct Mistake<'a> {
location: &'a str,
}
, , , , . , , .
, check()
Option<Mistake>
()
(, , β ..):
fn check(path: &str, s: &mut String) -> Result<Option<Mistake>, std::io::Error> {
use std::fs::File;
use std::io::Read;
s.clear();
File::open(path)?.read_to_string(s)?;
println!("~ {} ~", path);
Ok(match s.find(",") {
Some(index) => {
let location = &s[index..];
Some(Mistake { location })
}
None => None,
})
}
, . β , . match
β , Ok(match ...)
. , main()
:
fn main() -> Result<(), std::io::Error> {
let mut s = String::with_capacity(256 * 1024);
let result = check("sample.txt", &mut s)?;
if let Some(mistake) = result {
println!(": : {:?}", mistake.location);
}
Ok(())
}
" , result
", , " ". - if let
, , match
, !
!
$ cargo run --quiet
error[E0106]: missing lifetime specifier
--> src\main.rs:17:55
|
17 | fn check(path: &str, s: &mut String) -> Result<Option<Mistake>, std::io::Error> {
| ^^^^^^^ expected lifetime parameter
, . , :
: , ,path
s
.
? , Mistake
location: &'a str
β . s
β . :
// :
use std::io::Error as E;
fn check(path: &str, s: &'a mut String) -> Result<Option<Mistake<'a>>, E> {
// ...
}
, . 'a
? . ? , ? , ? , .
error[E0261]: use of undeclared lifetime name `'a`
--> src\main.rs:19:26
|
19 | fn check(path: &str, s: &'a mut String) -> Result<Option<Mistake<'a>>, E> {
| ^^ undeclared lifetime
error[E0261]: use of undeclared lifetime name `'a`
--> src\main.rs:19:66
|
19 | fn check(path: &str, s: &'a mut String) -> Result<Option<Mistake<'a>>, E> {
| ^^ undeclared lifetime
. ? :
struct Mistake<'a> {
location: &'a str,
}
, , 'a
location: &'a str
'a
Mistake<'a>
, Java. . Java Rust?
fn check<'a>(path: &str, s: &'a mut String) -> Result<Option<Mistake<'a>>, E> {
// ...
}
, !
$ cargo run --quiet
~ sample.txt ~
: : ", , !"
, :
fn main() -> Result<(), std::io::Error> {
let mut s = String::with_capacity(256 * 1024);
let path = "sample.txt";
let result = check(path, &mut s)?;
println!("~ {} ~", path);
if let Some(mistake) = result {
println!(": : {:?}", mistake.location);
}
Ok(())
}
struct Mistake<'a> {
location: &'a str,
}
use std::io::Error as E;
fn check<'a>(path: &str, s: &'a mut String) -> Result<Option<Mistake<'a>>, E> {
use std::fs::File;
use std::io::Read;
s.clear();
File::open(path)?.read_to_string(s)?;
Ok(match s.find(",") {
Some(index) => {
let location = &s[index..];
Some(Mistake { location })
}
None => None,
})
}
'a
, , . , . report()
:
fn main() -> Result<(), std::io::Error> {
let mut s = String::with_capacity(256 * 1024);
let path = "sample.txt";
let result = check(path, &mut s)?;
report(path, result);
Ok(())
}
fn report(path: &str, result: Option<Mistake>) {
println!("~ {} ~", path);
if let Some(mistake) = result {
println!(": : {:?}", mistake.location);
} else {
println!(" !!");
}
}
, Mistake
, Display
.
struct Mistake<'a> {
// !
path: &'static str,
location: &'a str,
}
, , , 'static
. , β . check()
:
fn check<'a>(path: &str, s: &'a mut String) -> Result<Option<Mistake<'a>>, E> {
use std::fs::File;
use std::io::Read;
s.clear();
File::open(path)?.read_to_string(s)?;
Ok(match s.find(",") {
Some(index) => {
let location = &s[index..];
// !
Some(Mistake { path, location })
}
None => None,
})
}
:
$ cargo run --quiet
error[E0621]: explicit lifetime required in the type of `path`
--> src\main.rs:37:28
|
27 | fn check<'a>(path: &str, s: &'a mut String) -> Result<Option<Mistake<'a>>, E> {
| ---- help: add explicit lifetime `'static` to the type of `path`: `&'static str`
...
37 | Some(Mistake { path, location })
| ^^^^ lifetime `'static` required
// new: &'static
fn check<'a>(path: &'static str, s: &'a mut String) -> Result<Option<Mistake<'a>>, E> {
// ...
}
Display
:
use std::fmt;
impl<'a> fmt::Display for Mistake<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}: : {:?}",
self.path, self.location
)
}
}
report()
:
fn report(result: Option<Mistake>) {
if let Some(mistake) = result {
println!("{}", mistake);
}
}
, :)
$ cargo run --quiet
sample.txt: : ", , !"
50 , , . , ?
, . !
fn main() -> Result<(), std::io::Error> {
let mut s = String::with_capacity(256 * 1024);
let paths = ["sample.txt", "sample2.txt", "sample3.txt"];
for path in &paths {
let result = check(path, &mut s)?;
report(result);
}
Ok(())
}
$ cargo run --quiet
sample.txt: : ", , !"
sample3.txt: : ", "
. , , . .
fn main() -> Result<(), std::io::Error> {
let mut s = String::with_capacity(256 * 1024);
let paths = ["sample.txt", "sample2.txt", "sample3.txt"];
//
let mut results = vec![];
for path in &paths {
let result = check(path, &mut s)?;
results.push(result);
}
//
for result in results {
report(result);
}
Ok(())
}
...
$ cargo run --quiet
error[E0499]: cannot borrow `s` as mutable more than once at a time
--> src\main.rs:9:34
|
9 | let result = check(path, &mut s)?;
| ^^^^^^ mutable borrow starts here in previous iteration of loop
. -? s
??
- . , , s
, &s
. β , , &mut s
. ? , s
:
fn check<'a>(path: &'static str, s: &'a mut String) -> Result<Option<Mistake<'a>>, E> {
// `s` -
s.clear();
// `s` - !
File::open(path)?.read_to_string(s)?;
// .
}
, . , ...
Rust.
.
, !
, Rust !!
.
. , , . memcpy()
, -.
Mistake
. location
, . Mistake
, . Rust?
β String
, , &str
. , , . .
struct Mistake<'a> {
// ,
path: &'static str,
// Mistake
location: String,
}
, , :
error[E0392]: parameter `'a` is never used
--> src\main.rs:27:16
|
27 | struct Mistake<'a> {
| ^^ unused parameter
|
= help: consider removing `'a` or using a marker such as `std::marker::PhantomData`
"-, ", , <'a>
. 'a
Display
check()
:
struct Mistake {
// ,
path: &'static str,
// Mistake
location: String,
}
// ...
impl fmt::Display for Mistake {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}: : {:?}",
self.path, self.location
)
}
}
// ...
fn check(path: &'static str, s: &mut String) -> Result<Option<Mistake>, E> {
s.clear();
File::open(path)?.read_to_string(s)?;
Ok(match s.find(",") {
Some(index) => {
let location = &s[index..];
Some(Mistake { path, location })
}
None => None,
})
}
, , :
error[E0308]: mismatched types
--> src\main.rs:43:34
|
43 | Some(Mistake { path, location })
| ^^^^^^^^
| |
| expected struct `std::string::String`, found &str
| help: try using a conversion method: `location: location.to_string()`
|
= note: expected type `std::string::String`
found type `&str`
. location
s
. , . , . , , "Rust" "", . β ?
" . ToString
, ToOwned
β , . :
fn check(path: &'static str, s: &mut String) -> Result<Option<Mistake>, E> {
s.clear();
File::open(path)?.read_to_string(s)?;
Ok(match s.find(",") {
Some(index) => {
let location = s[index..].to_string();
Some(Mistake { path, location })
}
None => None,
})
}
```bash
$ cargo run --quiet
sample.txt: : ", , !"
sample3.txt: : ", "
. , , , , .
Rust- , . , , . , , , , . β !
,
. , , , , , , . , , .
". . ."
. , , , ...
" ..." , . -, - , - , , . , , , , .
". . ."
struct Mistake {
path: &'static str,
locations: Vec<String>,
}
, ( , !). , , , , , , , , , -. , , find()
.
", find
, . - . ?"
'str
:
fn check(path: &'static str, s: &mut String) -> Result<Option<Mistake>, E> {
s.clear();
File::open(path)?.read_to_string(s)?;
let locations: Vec<_> = s
.match_indices(",")
.map(|(index, slice)| slice.to_string())
.collect();
Ok(if locations.is_empty() {
None
} else {
Some(Mistake { path, locations })
})
}
", if
!" , , . , , . map()
collect()
.
Display
, :
impl fmt::Display for Mistake {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for location in &self.locations {
write!(f, "{}: : {:?}\n", self.path, location)?;
}
Ok(())
}
}
sample.txt
:
, , ! ! Μ Μ β ! , . .
" , , ", . , .
$ cargo run --quiet
sample4.txt: : ","
sample4.txt: : ","
sample4.txt: : ","
. . . . . " location
, ?"
// check():
let locations: Vec<_> = s
.match_indices(",")
.map(|(index, _)| {
use std::cmp::{max, min};
let start = max(0, index - 12);
let end = min(index + 12, s.len());
s[start..end].to_string()
})
.collect();
.: . , , ASCII, 21 UTF- .
$ cargo run --quiet
sample4.txt: : ", "
sample4.txt: : " , !"
sample4.txt: : " , "
" , - ." . " , -, , , . check()
, report()
. - β , , ."
" , , , , ."
.
. check()
, report()
β .
. β , .
:
struct Mistake {
path: &'static str,
text: String,
locations: Vec<usize>,
}
fn check(path: &'static str) -> Result<Option<Mistake>, E> {
let text = std::fs::read_to_string(path)?;
let locations: Vec<_> = text.match_indices(",").map(|(index, _)| index).collect();
Ok(if locations.is_empty() {
None
} else {
Some(Mistake { path, text, locations })
})
}
. , , Mistake
, ( ?).
( UTF-8? β ..)
β Mistake
.
$ cargo run --quiet
warning: field is never used: `text`
--> src\main.rs:28:5
|
28 | text: String,
| ^^^^^^^^^^^^
|
= note: #[warn(dead_code)] on by default
sample4.txt: : 1
sample4.txt: : 19
sample4.txt: : 83
, - . . :
impl Mistake {
fn line_bounds(&self, index: usize) -> (usize, usize) {
let len = self.text.len();
let before = &self.text[..index];
let start = before.rfind("\n").map(|x| x + 1).unwrap_or(0);
let after = &self.text[index + 1..];
let end = after.find("\n").map(|x| x + index + 1).unwrap_or(len);
(start, end)
}
}
" rfind()
. , , , unwrap_or()
."
, :
impl fmt::Display for Mistake {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for &location in &self.locations {
let (start, end) = self.line_bounds(location);
let line = &self.text[start..end];
write!(f, "{}: :\n", self.path)?;
write!(f, "\n")?;
write!(f, " | {}", line)?;
write!(f, "\n\n")?;
}
Ok(())
}
}
$ cargo run --quiet sample4.txt: : | , , ! sample4.txt: : | , , ! sample4.txt: : | ,
. !
, - :
impl fmt::Display for Mistake {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for &location in &self.locations {
let (start, end) = self.line_bounds(location);
let line = &self.text[start..end];
let line_number = self.text[..start].matches("\n").count() + 1;
write!(f, "{}: :\n", self.path)?;
write!(f, "\n")?;
write!(f, "{:>8} | {}", line_number, line)?;
write!(f, "\n\n")?;
}
Ok(())
}
}
$ cargo run --quiet sample4.txt: : 1 | , , ! sample4.txt: : 1 | , , ! sample4.txt: : 6 | ,
( , Rust ?)
β , . , Rust, ^
. β Display
, , , , , . 11 ( 8 + |
, 3 ):
( technic93, utf-8, , , , . , β ..)
impl fmt::Display for Mistake {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for &location in &self.locations {
let (start, end) = self.line_bounds(location);
let line = &self.text[start..end];
let line_number = self.text[..start].matches("\n").count() + 1;
let comma_index = self.text[start..location].chars().count();
write!(f, "{}: :\n\n", self.path)?;
//
write!(f, "{:>8} | {}\n", line_number, line)?;
//
write!(f, "{}^\n\n", " ".repeat(11 + comma_index))?;
}
Ok(())
}
}
$ cargo run --quiet sample4.txt: : 1 | , , ! ^ sample4.txt: : 1 | , , ! ^ sample4.txt: : 6 | , ^
.
. 85 .
fn main() -> Result<(), std::io::Error> {
let paths = ["sample4.txt"];
//
let mut results = vec![];
for path in &paths {
let result = check(path)?;
results.push(result);
}
//
for result in results {
report(result);
}
Ok(())
}
fn report(result: Option<Mistake>) {
if let Some(mistake) = result {
println!("{}", mistake);
}
}
struct Mistake {
path: &'static str,
text: String,
locations: Vec<usize>,
}
use std::io::Error as E;
fn check(path: &'static str) -> Result<Option<Mistake>, E> {
let text = std::fs::read_to_string(path)?;
let locations: Vec<_> = text.match_indices(",").map(|(index, _)| index).collect();
Ok(if locations.is_empty() {
None
} else {
Some(Mistake {
path,
text,
locations,
})
})
}
use std::fmt;
impl Mistake {
fn line_bounds(&self, index: usize) -> (usize, usize) {
let len = self.text.len();
let before = &self.text[..index];
let start = before.rfind("\n").map(|x| x + 1).unwrap_or(0);
let after = &self.text[index + 1..];
let end = after.find("\n").map(|x| x + index + 1).unwrap_or(len);
(start, end)
}
}
impl fmt::Display for Mistake {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for &location in &self.locations {
let (start, end) = self.line_bounds(location);
let line = &self.text[start..end];
let line_number = self.text[..start].matches("\n").count() + 1;
let comma_index = self.text[start..location].chars().count();
write!(f, "{}: :\n\n", self.path)?;
//
write!(f, "{:>8} | {}\n", line_number, line)?;
//
write!(f, "{}^\n\n", " ".repeat(11 + comma_index))?;
}
Ok(())
}
}
, .
. , Rust. " Rust ", , , . , , , , .
( , FFI.)