(una traducción bastante gratuita de un gran artículo emocional que en la práctica une las posibilidades de C y Rust en términos de resolver problemas comerciales y resolver errores relacionados con la gestión manual de la memoria. También debería ser útil para personas con experiencia en la recolección de basura; existen muchas diferencias en términos de semántica menos de lo que parece - aprox. por. )
Desde el momento en que me interesé en Rust, me pareció una eternidad. Sin embargo, recuerdo claramente haber llegado a conocer el analizador de préstamos ( verificador de préstamos , en lo sucesivo denominado BC - aprox. Per. ), Acompañado de un dolor de cabeza y desesperación. Por supuesto, no soy el único que sufre: hay muchos artículos en Internet sobre el tema de la comunicación con las ojivas. Sin embargo, me gustaría destacar y destacar en este artículo la cabeza nuclear desde el punto de vista de los beneficios prácticos , y no solo un generador de dolor de cabeza.
De vez en cuando, me encuentro con opiniones que en Rust: administración manual de la memoria ( probablemente ya que no es automática con GC, entonces ¿qué más? - aproximadamente por persona ), pero no comparto este punto de vista en absoluto. El método utilizado en Rust, lo llamo " gestión de memoria declarativa ". Por qué, ahora lo mostraré.
En lugar de teorizar, escribamos algo útil.
¡Conoce Overbook - Fiction Publishing House!
Como cualquier editor, Overbook tiene reglas de diseño. Más precisamente, solo hay una regla, simple como puertas: sin comas . Overbuk cree sinceramente que las comas son consecuencia de la pereza de los derechos de autor y, citando, "deberían ser exterminadas como un fenómeno". Por ejemplo, la frase "Ella leyó y se rió": buena, adecuada. "Ella leyó, y luego se rió" - requiere corrección.

Sin embargo, parecería más simple en ninguna parte, sin embargo, ¡los autores se dan cuenta regularmente en Overbuk sobre el incumplimiento patológico de esta regla! Como si tal regla no existiera, ¡escandalosa! Tenemos que verificar dos veces todo. Manualmente Además, si el editor solicita un borrador para su edición, el autor puede enviar una versión que se ha corregido en uno, pero que está dañada en otro lugar, y por lo tanto, todo debe ser revisado desde el principio. Las empresas no toleran una actitud tan negligente con respecto al tiempo de trabajo, y surgió la necesidad de automatizar el proceso de capturar la "pereza del autor" por sí mismo. Por ejemplo, un programa de computadora. Si si?
Robin se apresura al rescate
Robin es uno de los empleados de la editorial que se ofreció como voluntario para ayudar a escribir el programa, porque conocía la programación: ¡es buena suerte! Es cierto que a todos en la universidad, incluido Robin, se les enseñó C y Java, y se prohibió irracionalmente a Java VM instalar Java desde la editorial, ¡ese es el turno! Bueno, que sea C, se ha escrito mucho en C, el lenguaje es seguro y verificado. Todo saldrá como debería, 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
.
. " !"
struct Mistake {
char *message;
};
struct CheckResult {
char *path;
struct Mistake *mistake;
};
struct CheckResult check(char *path, char *buf) {
struct CheckResult result;
result.path = path;
result.mistake = NULL;
return result;
}
" " — — " , 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';
return result;
}
, *.txt, . - , , ...
, TODO — ? . :
#include <stdlib.h>
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';
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) {
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 {
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() {
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 ~
!!
- . , , :
— , . ! , - — !
- , , , , , :
— , . . .
. — . — .
" -?" — .
#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));
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) => {
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
256 , .- — , : , .
- ,
#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
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.clear();
File::open(path)?.read_to_string(s)?;
}
, . , ...
Rust.
.
, !
, Rust !!
.
. , , . memcpy()
, -.
Mistake
. location
, . Mistake
, . Rust?
— String
, , &str
. , , . .
struct Mistake<'a> {
path: &'static str,
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,
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" "", . — ?
" malloc()
. , - ! ?" , . :

" . 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
, ?"
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:
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.)