thảo luận Nhật kí tự học lập trình Rust - phát triển ứng dụng Desktop (Both CLI + GUI)

  • Người tạo chủ đề Người tạo chủ đề duykhanh471
  • Ngày bắt đầu Ngày bắt đầu
pub fn check_broken_image<P: AsRef<Path> + Copy>(&self, img_path: P) -> Option<BrokenImageInfo<P>>{
Nếu sử dụng AsRef<Path> thì tức là chỉ có họ Path mới có thể được sử dụng kiểu Path với PathBuf đúng không bác? Mình thấy khá thắc mắc là kiểu như tại sao &String lại đặt thay cho &str được hay cũng như &PathBuf đặt thay cho &Path được
 
Mình thấy khá thắc mắc là kiểu như tại sao &String lại đặt thay cho &str được hay cũng như &PathBuf đặt thay cho &Path được
bạn có thể đọc về khái niệm deref coercion, nói chung là type T nào implement trait Deref<Target = U> thì Rust có thể tự động cast reference tới type T sang reference tới type U,

ví dụ như String có implement Deref<str> nên Rust có thể tự động cast &String sang &str
 
Nếu sử dụng AsRef<Path> thì tức là chỉ có họ Path mới có thể được sử dụng kiểu Path với PathBuf đúng không bác? Mình thấy khá thắc mắc là kiểu như tại sao &String lại đặt thay cho &str được hay cũng như &PathBuf đặt thay cho &Path được
Để đơn giản, thím cứ tưởng tượng Path nó như kiểu str (thực ra trong cài đặt nó cũng không khác mấy), nó không có size (chính xác hơn là size không xác định được trong runtime) nên chỉ có thể sử dụng được thông qua reference đến nó. Constraint AsRef<Path> tức là yêu cầu một kiểu sao cho nó có thể convert thành kiểu reference đến Path.

Còn &String có thể biến đổi thành &str (cũng như &PathBuf có thể biến đổi thành &str) là do String được cài đặt trait Deref<Target = str> (trait Deref dùng để biến reference của một kiểu thành reference của một kiểu khác).

Còn tại sao có lúc dùng &String và có lúc lại dùng &str thì phụ thuộc xem hàm đó làm gì. Ví dụ như một hàm mà chỉ cần duyệt qua chuỗi, thì tham số cho hàm đó chỉ cần là &str (mà không cần là &String). Điều này làm cho hàm có thể sử dụng được ở nhiều ngữ cảnh hơn.
 
Sửa lần cuối:
Nếu sử dụng AsRef<Path> thì tức là chỉ có họ Path mới có thể được sử dụng kiểu Path với PathBuf đúng không bác?

function signature check_broken_image<P: AsRef<Path> có nghĩa là type P này phải implement trait AsRef<Path>, tức là nó phải có hàm as_ref trả về một reference tới path, phần nằm giữa hai angle bracket trước argument list (<P: AsRef<Path>) là ràng buộc type

khi bạn viết check_broken_image<P: AsRef<Path>(&self, img_path: P) thì bạn đang viết một generic function, tùy theo type thật của argument img_path trong quá trình sử dụng Rust sẽ tự động gen ra một hàm nhận argument img_path với type thực đó,

lấy ví dụ trong code của bạn gọi hàm check_broken_image trong image_processing và pass argument với type là PathBuf

C-like:
pub fn image_processing(paths: impl IntoIterator<Item = PathBuf>, img_processor: ImageProcessor) {
    for current_path in paths {
        if let Some(broken_img) = img_processor.check_broken_image(&current_path) {
            println!("Image: {} | {}", broken_img.path.display(), broken_img.err_msg);
        };
    }
}

thì trong trường hợp này bạn có thể hiểu là Rust sẽ gen ra hàm check_broken_image này với signature check_broken_image(&self, img_path: PathBuf), Rust chấp nhận gen ra như vầy do PathBuf có implement trait AsRef<Path>
 
Sửa lần cuối:
Thanks hai bác @small-lambda@Konstante gất nhìu. Thì mình xin phép tổng hợp lại các thông tin và làm nốt con static site generator :> Mấy hôm nay nghỉ nên hơi lười
ví dụ như String có implement Deref<str> nên Rust có thể tự động cast &String sang &str
Vậy thì đơn giản hóa là Defer là một Trait giúp chuyển đổi tự động đúng bác nên một số dạng như String hay PathBuf mà sử dụng & sẽ được tự động chuyển về str với Path.
nên chỉ có thể sử dụng được thông qua reference đến nó
Tức là về cơ bản sẽ được sử dụng &str hoặc &Path chứ không được dùng mà không có reference đúng không bác. Tức là size không xác định nên buộc phải dựa trên reference
Còn tại sao có lúc dùng &String và có lúc lại dùng &str thì phụ thuộc xem hàm đó làm gì. Ví dụ như một hàm mà chỉ cần duyệt qua chuỗi, thì tham số cho hàm đó chỉ cần là &str (mà không cần là &String). Điều này làm cho hàm có thể sử dụng được ở nhiều ngữ cảnh hơn.
Tức là ví dụ khi Code đặt biến đầu vào là name: &str thì có thể nhận cả &str lẫn &String nhưng là &String thì bắt buộc phải là &String? Có phải đấy là ý của bác khi nói nhiều ngữ cảnh hơn không?
function signature check_broken_image<P: AsRef<Path> có nghĩa là type P này phải implement trait AsRef<Path>, tức là nó phải có hàm as_ref trả về một reference tới path, phần nằm giữa hai angle bracket trước argument list (<P: AsRef<Path>) là ràng buộc type
Tức khi implement trait AsRef<Path> thì bị ràng buộc kiểu biến nên khi sử dụng as_ref() nó phải lấy kiểu đã implement trait ở trên và trả Path?
thì trong trường hợp này bạn có thể hiểu là Rust sẽ gen ra hàm check_broken_image này với signature check_broken_image(&self, img_path: PathBuf), Rust chấp nhận gen ra như vầy do PathBuf có implement trait AsRef<Path>
Bác đã giải thích câu ở trước rùi :>

Sắp tới sẽ nhờ hai bác tiếp, đang nâng cấp con static site gen.
 
Thanks hai bác @small-lambda@Konstante gất nhìu. Thì mình xin phép tổng hợp lại các thông tin và làm nốt con static site generator :> Mấy hôm nay nghỉ nên hơi lười

Vậy thì đơn giản hóa là Defer là một Trait giúp chuyển đổi tự động đúng bác nên một số dạng như String hay PathBuf mà sử dụng & sẽ được tự động chuyển về str với Path.

Tức là về cơ bản sẽ được sử dụng &str hoặc &Path chứ không được dùng mà không có reference đúng không bác. Tức là size không xác định nên buộc phải dựa trên reference

Tức là ví dụ khi Code đặt biến đầu vào là name: &str thì có thể nhận cả &str lẫn &String nhưng là &String thì bắt buộc phải là &String? Có phải đấy là ý của bác khi nói nhiều ngữ cảnh hơn không?

Tức khi implement trait AsRef<Path> thì bị ràng buộc kiểu biến nên khi sử dụng as_ref() nó phải lấy kiểu đã implement trait ở trên và trả Path?

Bác đã giải thích câu ở trước rùi :>

Sắp tới sẽ nhờ hai bác tiếp, đang nâng cấp con static site gen.
Bác đừng nghĩ phức tạp quá, cái tham số impl Deref<str> hay impl AsRef<Path> nó chỉ mô tả tham số của cái hàm là generic (tức là type gì cũng đc miễn là được implement trait Deref hay AsRef), bác có thể tự định nghĩa trait Convert chẳng hạn cũng chả sao.
Còn tại sao lại là 2 traits này, đơn giản nó là Syntactic sugar thôi, AsRef là SS của & còn Deref là của *, đại khái là thằng nào implement AsRef thì khi dùng & sẽ gọi hàm as_ref() còn thằng nào implement Deref thì khi dùng với * nó sẽ gọi hàm deref() thế thôi.
Bác viết let a: &str = String::new().as_ref() hay let a: &str = &String::new(); là như nhau
Ví dụ phổ biến khác là khi impl trait Add thì bác sẽ dùng được phép A + B hoặc gọi A.add(B)

Edit: chỗ ví dụ chỗ String là sai, do String implement cả Deref và AsRef<str>, ví dụ 1 gọi as_ref() và ví dụ 2 gọi deref()
 
Sửa lần cuối:
bạn có link gì nói về vụ syntactic sugar này không, vì mình tìm không thấy
Bác tham khảo mấy cái trait ở link này

  • Deref
    Used for immutable dereferencing operations, like *v.
  • DerefMut
    Used for mutable dereferencing operations, like in *v = 1;.

Trait AsRef thì riêng
 
Bác tham khảo mấy cái trait ở link này

  • Deref
    Used for immutable dereferencing operations, like *v.
  • DerefMut
    Used for mutable dereferencing operations, like in *v = 1;.

Trait AsRef thì riêng
mình nghĩ bạn hiểu nhầm cách DerefAsRef hoạt động rồi, theo mình hiểu Deref chỉ làm chuyện tự động cast rereference của một type này sang một type khác thôi, ngay trong đoạn doc giải thích về deref coercion có nói:

If T implements Deref<Target = U>, and v is a value of type T, then:
  • In immutable contexts, *v (where T is neither a reference nor a rawpointer) is equivalent to *Deref::deref(&v).
  • Values of type &T are coerced to values of type &U
  • T implicitly implements all the methods of the type U which take the &self receiver.

  • chấm đầu tiên mình hiểu là khi có reference tới một type A nào đó mà type A này có implement trait Deref với Target = B thì khi bạn dùng dereference operator * Rust sẽ tự động cast cái reference tới type A đó sang reference tới type B rồi mới áp dụng dereference operator lên kết quả cast
  • chấm thứ hai và thứ ba là Rust làm chuyện tự động cast reference khi nào phù hợp, ví dụ method nhận một reference tới string slice (&str) nhưng bạn truyền vào reference tới (&String) thì Rust tự động convert reference đó sang &str

mình coi thử doc của trait AsRef và không thấy nhắc gì tới syntactic sugar, trước giờ cái trait này mình hiểu làm cho chuyện chuyển đổi từ reference type này sang reference tới type khác được thể hiện rõ thôi, thay vì để cho Rust tự động cast như trên thông qua Deref thì bạn gọi hàm as_ref trên &String chẳng hạn
 
mình nghĩ bạn hiểu nhầm cách DerefAsRef hoạt động rồi, theo mình hiểu Deref chỉ làm chuyện tự động cast rereference của một type này sang một type khác thôi, ngay trong đoạn doc giải thích về DerefCoercion có nói:



  • chấm đầu tiên mình hiểu là khi có reference tới một type A nào đó mà type A này có implement trait Deref với Target = B thì khi bạn dùng dereference operator * Rust sẽ tự động cast cái reference tới type A đó sang reference tới type B rồi mới áp dụng dereference operator lên kết quả cast
  • chấm thứ hai và thứ ba là Rust làm chuyện tự động cast reference khi nào phù hợp, ví dụ method nhận một reference tới string slice (&str) nhưng bạn truyền vào reference tới (&String)

mình coi thử doc của trait AsRef và không thấy nhắc gì tới syntactic sugar, trước giờ cái trait này mình hiểu làm cho chuyện chuyển đổi từ reference type này sang reference tới type khác được thể hiện rõ thôi, thay vì để cho Rust tự động cast như trên thông qua Deref thì bạn gọi hàm as_ref trên &String chẳng hạn
Nhiệm vụ của AsRef và Deref là để convert, còn convert thế nào là tùy người viết, nó chỉ là 1 trait trong thư viện standard của Rust, nó cast gì thì cast, cái đó mình ko quan tâm, cái mình quan tâm ở đây là khi mình dùng & thì nó tự call hàm as_ref(), còn * thì nó call hàm deref(), vậy thì nó chính là 1 syntactic sugar, giống như ? vậy, nó sẽ check và kiểm tra error, convert error cho bạn, vậy nó là 1 syntactic sugar, nếu bác cảm thấy gọi như vậy không ổn lắm thì cứ gọi nó là operator đi cũng được

let a: &str = &String::new(); sẽ tương đương với let a = <String as AsRef<str>>::as_ref(&String::new());

Edit: đây là nhận định sai, các bác bỏ qua nhé
 
Sửa lần cuối:
gọi hàm as_ref() còn thằng nào implement Deref thì khi dùng với * nó sẽ gọi hàm deref() thế thôi
mình nghĩ đoạn này có thể hiểu nhầm là khi dùng dereference operator * thì hàm deref là đứa làm chuyện dereference, chứ không phải là chỉ đi cast
 
vầy xin hỏi là bạn đọc doc chỗ nào có nói là có vụ tự động gọi hàm này, vì mình không tìm thấy
trong cái link mình gửi ở trên của trait AsRef luôn mà
ví dụ của nó ấy

let x = Box::new(5i32);
// Avoid this:
// let y: &i32 = x.as_ref();
// Better just write:
let y: &i32 = &x;
 
trong cái link mình gửi ở trên của trait AsRef luôn mà
ví dụ của nó ấy

let x = Box::new(5i32);
// Avoid this:
// let y: &i32 = x.as_ref();
// Better just write:
let y: &i32 = &x;
cái ví dụ đó mình hiểu như vầy: doc khuyên bạn không nên gọi hàm as_ref để làm chuyện cast reference mà nên dùng deref coercion để Rust tự động cast, vì ngay trước ví dụ đó có câu này:

However, AsRef::as_ref should not be used for the sole purpose of dereferencing; instead ‘Deref coercion’ can be used:

và dòng let y: &i32 = &x mình hiểu là rust tự động cast reference từ type &Box<i32> sang type &i32, chứ ví dụ đó không phải là ví dụ cho syntactic sugar như bạn nói
 
cái ví dụ đó mình hiểu như vầy: doc khuyên bạn không nên gọi hàm as_ref để làm chuyện cast reference mà nên dùng deref coercion để Rust tự động cast, vì ngay trước ví dụ đó có câu này:



và dòng let y: &i32 = &x mình hiểu là rust tự động cast reference từ type &Box<i32> sang type &i32, chứ ví dụ đó không phải là ví dụ cho syntactic sugar như bạn nói
à, bạn đúng rồi, mình bị nhầm ở chỗ này, &T call deref() chứ ko phải call as_ref(), và *T là dereference cái reference của hàm deref()
vậy AsRef phải gọi thủ công as_ref(), sorry và cảm ơn bạn đã chỉ ra chỗ này nhé, mình ẩu thật sự.

Chốt lại là struct A implement Deref target B
thì
let a = A;
let b: &B = &A sẽ tự call deref()
còn *a lúc này là dereference &B

p/s: mình có thử test lại rồi

Ruby:
struct Test(i32);

impl Deref for Test {
    type Target = i32;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

let test = Test(10);
let a = &test // = &Test(10) không gọi deref() ở đây
let a: &i32 = &test; // = &10 tự call deref()
let a = *test; // = 10 dereference cái ref trả về của deref()
 
Sửa lần cuối:
Nhiệm vụ của AsRef và Deref là để convert, còn convert thế nào là tùy người viết, nó chỉ là 1 trait trong thư viện standard của Rust, nó cast gì thì cast, cái đó mình ko quan tâm, cái mình quan tâm ở đây là khi mình dùng & thì nó tự call hàm as_ref(), còn * thì nó call hàm deref(), vậy thì nó chính là 1 syntactic sugar, giống như ? vậy, nó sẽ check và kiểm tra error, convert error cho bạn, vậy nó là 1 syntactic sugar, nếu bác cảm thấy gọi như vậy không ổn lắm thì cứ gọi nó là operator đi cũng được

let a: &str = &String::new(); sẽ tương đương với let a = <String as AsRef<str>>::as_ref(&String::new());
Không phải đâu bạn, thử ví dụ sau đây:
C-like:
struct L;
impl Deref for L {
    type Target = str;
    fn deref(&self) -> &Self::Target {
        "hello"
    }
}

impl AsRef<str> for L {
    fn as_ref(&self) -> &str {
        "world"
    }
}

static WHAT: String = String::new();
impl AsRef<String> for L {
    fn as_ref(&self) -> &String {
        &WHAT
    }
}


Bây giờ xét khai báo sau:
C-like:
let l = &L;
Thế thì l sẽ có kiểu gì? Là &L hay &str hay &String? Ở đây l phải có kiểu &L (không thể có syntactic sugar tự động gọi một hàm ngẫu nhiên as_ref nào đó từ hai impl AsRef<str> và impl AsRef<String>).

Tiếp theo xét khai báo và lệnh print sau:
C-like:
let s: &str = &L;
println!("{}", s);
Thế thì kết quả in ra màn hình là "hello" hay là "world"? Ở đây phải là "hello" vì as_ref<str> của L không thể tự động chạy nếu người lập trình không chủ động cho nó chạy, chỉ có deref<Target = str> mới được phép tự động chạy (với điều kiện là kiểu của s phải được chỉ định rõ ràng là &str).
 
Sửa lần cuối:
Không phải đâu bạn, thử ví dụ sau đây:
C-like:
struct L;
impl Deref for L {
    type Target = str;
    fn deref(&self) -> &Self::Target {
        "hello"
    }
}

impl AsRef<str> for L {
    fn as_ref(&self) -> &str {
        "world"
    }
}

static WHAT: String = String::new();
impl AsRef<String> for L {
    fn as_ref(&self) -> &String {
        &WHAT
    }
}


Bây giờ xét khai báo sau:
C-like:
let l = &L;
Thế thì l sẽ có kiểu gì? Là &L hay &str hay &String? Ở đây l phải có kiểu &L (không thể có syntactic sugar tự động gọi một hàm ngẫu nhiên as_ref nào đó từ hai impl AsRef<str> và impl AsRef<String>).

Tiếp theo xét khai báo và lệnh print sau:
C-like:
let s: &str = &L;
println!("{}", s);
Thế thì kết quả in ra màn hình là "hello" hay là "world"? Ở đây phải là "hello" vì as_ref<str> của L không thể tự động chạy nếu người lập trình không chủ động cho nó chạy, chỉ có deref<Target = str> mới được phép tự động chạy (với điều kiện là kiểu của s phải được chỉ định rõ ràng là &str).
cảm ơn bác, mình đã đính chính ở bài post dưới rồi mà, để mình edit lại bài này ko lại gây tranh cãi
 

Thống kê chủ đề

Ngày tạo
duykhanh471,
Người trả lời cuối
emlameo4`,
Trả lời
142
Lượt xem
27.186
Quay lại
Lên đầu trang