New FAMILUG

The PyMiers

Monday, 11 December 2023

24 ngày học Rust - ngày 11 for loop borrow

for sẽ move value

Khi lặp qua từng phần tử của v với for i in v, v bị move ownership cho vòng for loop, nên ở lần lặp thứ 2 không còn v nữa, Rust compiler báo lỗi. Thấy thêm rằng khi người dùng viết for i in v thì code thực sự được chạy là for i in v.into_iter()

fn main() {

    let v = vec![10,20,30,40];
    for i in v {
        println!("{}", i);
    }
    for i in v {
        println!("{}", i);
    }

}

Output

error[E0382]: use of moved value: `v`
 --> src/main.rs:6:14
  |
2 |     let v = vec![10,20,30,40];
  |         - move occurs because `v` has type `Vec<i32>`, which does not implement the `Copy` trait
3 |     for i in v {
  |              - `v` moved due to this implicit call to `.into_iter()`
...
6 |     for i in v {
  |              ^ value used here after move
  |
note: `into_iter` takes ownership of the receiver `self`, which moves `v`
 --> /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/core/src/iter/traits/collect.rs:262:18
help: consider iterating over a slice of the `Vec<i32>`'s content to avoid moving into the `for` loop
  |
3 |     for i in &v {
  |              +

Fix: để có thể lặp qua v 2 lần, borrow v khi dùng với for:

    let v = vec![10,20,30,40];
    let mut v2: Vec<i32> = vec![];
    for i in &v {
        v2.push(i);
    }
    for i in &v {
        v2.push(i);
    }

Lần này compiler lại báo lỗi, nhưng không phải do v nữa mà ở i:

 --> src/main.rs:8:17
  |
8 |         v2.push(i);
  |            ---- ^ expected `i32`, found `&{integer}`
  |            |
  |            arguments to this method are incorrect
  |
note: method defined here
 --> /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/vec/mod.rs:1836:12
help: consider dereferencing the borrow
  |
8 |         v2.push(*i);

khi borrow v thì i cũng là borrow, để thêm i vào vector v2 type Vec, phải dereferencing i:

  v2.push(*i);

Trông sẽ không được đẹp mắt, ví dụ khi thêm 1 tuple vào v3:

    let v = vec![10,20,30,40];
    let mut v3: Vec<(i32, i32)> = vec![];
    for i in &v {
        v3.push((*i, *i));
    }

Có thể dereferencing i ngay tại vòng for:

    for &i in &v {
        v3.push((i, i));
    }

Kết luận

for thực hiện move giá trị, borrow để dùng nhiều lần.

Ủng hộ tác giả 🍺

Sunday, 10 December 2023

Máy không có IPv6, ai query DNS record AAAA?

Đa phần các máy tính vào cuối 2023 đều đã sử dụng IPv6, đặc biệt là các Linux server. Nhưng khi đã tắt IPv6, disable qua sysctl:

# sysctl -w net.ipv6.conf.all.disable_ipv6=1

bật tcpdump vẫn thấy các ứng dụng query AAAA record (lấy địa chỉ IPv6 tương ứng với domain), tại sao?

Thí nghiệm

Mở 1 cửa sổ chạy tcpdump port 53:

root@box:~# tcpdump port 53
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
...

Mở cửa sổ khác, bật Python3, import socket và gọi các function để lấy địa chỉ IP từ tên miền:

>>> import socket
>>> socket.gethostbyname("pymi.vn")
'104.21.61.168'
>>> socket.gethostbyname("pymi.vn")
'172.67.212.45'
>>> socket.getaddrinfo("familug.org")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: getaddrinfo() missing 1 required positional argument: 'port'
>>> socket.getaddrinfo("familug.org", 443)
[(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('216.239.32.21', 443)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('216.239.32.21', 443)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_RAW: 3>, 0, '', ('216.239.32.21', 443))]
>>>

Đoạn code trên sử dụng gethostbyname, một function cổ điển để lấy IP, tương ứng với function trên C, xem man 3 gethostbyname.

Kết quả tcpdump sẽ chỉ có IPv4:

12:21:25.019583 IP box.41423 > 192.168.122.1.domain: 57705+ A? pymi.vn. (25)
12:21:25.020054 IP 192.168.122.1.domain > box.41423: 57705 2/0/0 A 172.67.212.45, A 104.21.61.168 (57)

Còn khi query familug.org, sử dụng function getaddrinfo kèm them port 443, kết quả

12:21:39.722169 IP box.49228 > 192.168.122.1.domain: 29210+ A? familug.org. (29)
12:21:39.722208 IP box.49228 > 192.168.122.1.domain: 23059+ AAAA? familug.org. (29)
12:21:39.889210 IP 192.168.122.1.domain > box.49228: 29210 1/0/0 A 216.239.32.21 (45)
12:21:39.896108 IP 192.168.122.1.domain > box.49228: 23059 0/1/0 (90)

thấy có query id 23059 AAAA? familug.org. và kết quả 0/1/0.

gethostbyname đã rất cũ và hiện nay được coi là obsoleted (lỗi thời), các ứng dụng hiện đại đều được khuyên dùng getaddrinfo.

The obsolescent h_errno external integer, and the obsolescent gethostbyaddr() and gethostbyname() functions are removed, along with the HOST_NOT_FOUND, NO_DATA, NO_RECOVERY, and TRY_AGAIN macros. https://pubs.opengroup.org/onlinepubs/9699919799/

Vậy nên dù máy không có IPv6, ứng dụng vẫn gọi getaddrinfo, function này vẫn sẽ lấy địa chỉ IPv6 của domain tương ứng.

Và có vẻ như không thể dễ dàng disable tính năng nay, tức các ứng dụng sẽ luôn query IPv6 dù muốn hay không.

Kết luận

AAAA là DNS record IPv6, getaddrinfo luôn lấy cả 4 và 6... để được điểm 10.

Tham khảo

Ủng hộ tác giả 🍺

Wednesday, 6 December 2023

24 ngày học Rust - ngày 2 set

Kiểu set chỉ chứa mỗi phần tử 1 lần duy nhất, loại bỏ các phần tử trùng lặp. Ví dụ sau chỉ thấy có 1 số 1.

HashSet - kiểu dữ liệu set

Rust có sẵn kiểu set có tên HashSet, cần phải import từ thư viện "collections"

use std::collections::HashSet;
pub fn main() {
    let mut s = HashSet::new();
    s.insert(1);
    s.insert(2);
    s.insert(3);
    s.insert(1);
    dbg!(&s);
}

//[src/main.rs:8] &s = {
//    2,
//    1,
//    3,
//}

Kiểu set không có thứ tự của các phần tử, kết quả print có thể khác ví dụ trên và khác ở mỗi lần chạy code.

Tạo set từ vector, iterator

let cs = "abcddcba".chars();
let s1: HashSet<char> = HashSet::from_iter(cs);
dbg!(s1);

// s1 = {
//    'd',
//    'a',
//    'c',
//    'b',
//}

let v = vec![1, 2, 3, 1];
let s2: HashSet<i64> = HashSet::from_iter(v);
dbg!(s2);

// s2 = {
//    3,
//    1,
//    2,
//}

HashSet có đủ mọi method cho set

Như Python, Rust HashSet có đủ các method

  • union (Python |)
  • difference (Python -)
  • contains (Python >)
  • intersection (Python &)
  • symmetric_difference (Python ^)
let s2: HashSet<i64> = HashSet::from_iter(vec![1, 2, 3, 1]);
let s3: HashSet<i64> = HashSet::from_iter(vec![2, 3, 5]);

// pub fn symmetric_difference<'a>(&'a self, other: &'a HashSet<T, S>) -> SymmetricDifference<'a, T, S>
// s2.symmetric_difference(&s3) = [
//    1,
//    5,
//]

Tạo vector từ HashSet

let v = Vec::from_iter(s3);
dbg!(&v);
// &v = [
//    5,
//    2,
//    3,
//]

Kết luận

Rust tuy gõ type dài hơn một chút, nhưng vẫn có set tiện như Python. Ngôn ngữ nào không có kiểu set builtin? Go!

Ủng hộ tác giả 🍺

Saturday, 2 December 2023

24 ngày học Rust - ngày 1 String

Loạt bài viết giới thiệu các khái niệm trong Rust. Không quá chi tiết, đa phần ngắn gọn, mục tiêu trong 24 ngày.

PS: Là note khi giải https://adventofcode.com

Cài đặt & chạy code

Tham khảo https://pp.pymi.vn/article/aoc2021/

Bài viết sử dụng Rust 2021.

String, &str

Rust có 2 kiểu string thường dùng là str và String.

str - string slice

str còn được gọi là string slice, thường được dùng ở dạng "borrowed" &str. Kiểu của các string khi viết trực tiếp vào code (literal string) là &str. str dùng double quote " để bao quanh nội dung:

let s: &str = "Hello, world!";

String

String là một UTF-8 string có thể thay đổi.

A UTF-8–encoded, growable string.

String làm chủ sở hữu giá trị của nó nên còn gọi là owned string - trái với &strborrowed (đi mượn).

let s: String = String::from("Hello, world!");
dbg!(s);
// [src/main.rs:6] s = "Hello, world!"

Thêm (append) vào String với push(char) hay push_str(&str).

let mut res: String = String::from("Hello, world!");
res.push('\t');
res.push_str("How are you?");
// [src/main.rs:10] res = "Hello, world!\tHow are you?"

String vs &str

String&str có nhiều method giống nhau: starts_with, ends_with, split, replace, is_digit...

Sự khác biệt chủ yếu ở mục đích sử dụng:

  • Dùng String khi tạo string mới hay nội dung string thay đổi.
  • Dùng &str khi nội dung string cố định, không mới.

Method replace cho thấy sự khác biệt này:

pub fn replace<'a, P>(&'a self, from: P, to: &str) -> String
where
    P: Pattern<'a>,

"a b c a".replace("a", "d") trả về 1 String. Có thể hiểu rằng do Rust cần cấp phát bộ nhớ để tạo ra 1 String có nội dung mới, nên đây là kiểu String thay vì &str.

Đọc nội dùng 1 file cũng trả về String:

let contents = std::fs::read_to_string("/etc/passwd").unwrap();
pub fn read_to_string<P>(path: P) -> io::Result<String>
where
    P: AsRef<Path>,

Dùng "abc".to_string() để biến &str thành String, ngược lại String::from("abc").as_str() để biến String thành &str.

Split

"a-b-c".split("-") trả về một Split struct, hay full name std::str::Split chứ không phải 1 vector Vec<&str>.

pub fn split<'a, P>(&'a self, pat: P) -> Split<'a, P>
where
    P: Pattern<'a>,

Split implement Iterator trait (hay nói như Python: list là 1 iterable, có method __iter__)

nên có thể duyệt qua từng phần tử của kết quả, mỗi phần tử là 1 &str cho dù s&str hay String:

let sp: std::str::Split<&str>  = "bacadae".split("a");
for p in sp {
  dbg!(p);
}
let s = String::from("bacadae");
for p in s.split("a") {
  dbg!(p);
}
//[src/main.rs:16] p = "b"
//[src/main.rs:16] p = "c"
//[src/main.rs:16] p = "d"
//[src/main.rs:16] p = "e"

filter method trả về 1 Filter

let s = String::from("bacadae");
// Filter<Split<&str>, |&&str| -> bool>
let ps = s.split("a").filter(|&s| s > "c");
for r in ps {
  dbg!(r); // r là &str
}
// [src/main.rs:17] r = "d"
// [src/main.rs:17] r = "e"

Để thu được Vec<&str> dùng collect():

let s = "  a  b\tc\nd    ";
// pub fn split_whitespace(&self) -> SplitWhitespace<'_>
let parts: Vec<&str> = s.split_whitespace().collect();
dbg!(parts);
// [src/main.rs:19] parts = [
//     "a",
//     "b",
//     "c",
//     "d",
// ]

char - character

char là một ký tự, hay chính xác hơn là 1 Unicode scalar value, dùng single quote ' để bao quanh 2 bên char.

let c: char = '\u{1b0}'; // ư
let c2 = 'a';

Biến vector char thành String với collect:

let v = vec!['a', 'b', 'c'];
let s: String = v.iter().collect();

Kết luận

Khi nội dung string được tạo mới, hoặc thay đổi, dùng String, khi nội dung cố định, không mới, dùng &str.

Ủng hộ tác giả 🍺