Bạn học bằng cách so sánh với những gì bạn đã biết. Gần đây tôi đã bị nhầm lẫn khi giả định Rust hoạt động giống như Java về việc giải quyết phiên bản phụ thuộc bắc cầu. Trong bài viết này, tôi muốn so sánh hai ngôn ngữ này.Bạn học bằng cách so sánh với những gì bạn đã biết. Gần đây tôi đã bị nhầm lẫn khi giả định Rust hoạt động giống như Java về việc giải quyết phiên bản phụ thuộc bắc cầu. Trong bài viết này, tôi muốn so sánh hai ngôn ngữ này.

Giải quyết phụ thuộc chuyển tiếp trong Rust và Java: So sánh hai ngôn ngữ

2025/09/21 23:00

Bạn học bằng cách so sánh với những gì bạn đã biết. Gần đây tôi đã bị ảnh hưởng bởi giả định rằng Rust hoạt động giống như Java về giải quyết phiên bản phụ thuộc bắc cầu. Trong bài viết này, tôi muốn so sánh cả hai.

Phụ thuộc, Tính bắc cầu và Giải quyết phiên bản

Trước khi đi sâu vào chi tiết của từng ngăn xếp, hãy mô tả lĩnh vực và các vấn đề đi kèm với nó.

\ Khi phát triển bất kỳ dự án nào trên mức Hello World, có khả năng bạn sẽ gặp phải những vấn đề mà người khác đã từng gặp trước đó. Nếu vấn đề phổ biến, khả năng cao là có ai đó đủ tốt bụng và có ý thức công dân để đóng gói mã giải quyết vấn đề đó, cho người khác sử dụng lại. Bây giờ, bạn có thể sử dụng gói đó và tập trung vào việc giải quyết vấn đề cốt lõi của mình. Đó là cách ngành công nghiệp xây dựng hầu hết các dự án ngày nay, ngay cả khi nó mang lại các vấn đề khác: bạn đứng trên vai những người khổng lồ.

\ Các ngôn ngữ đi kèm với các công cụ xây dựng có thể thêm các gói như vậy vào dự án của bạn. Hầu hết chúng đều gọi các gói bạn thêm vào dự án là phụ thuộc. Đến lượt mình, các phụ thuộc của dự án có thể có phụ thuộc riêng của chúng: cái sau được gọi là phụ thuộc bắc cầu.

Transitive dependencies

Trong sơ đồ trên, C và D là phụ thuộc bắc cầu.

\ Phụ thuộc bắc cầu có các vấn đề riêng của chúng. Vấn đề lớn nhất là khi một phụ thuộc bắc cầu được yêu cầu từ các đường dẫn khác nhau, nhưng ở các phiên bản khác nhau. Trong sơ đồ dưới đây, cả A và B đều phụ thuộc vào C, nhưng ở các phiên bản khác nhau của nó.

Phiên bản nào của C mà công cụ xây dựng nên bao gồm trong dự án của bạn? Java và Rust có câu trả lời khác nhau. Hãy mô tả chúng lần lượt.

Giải quyết phiên bản phụ thuộc bắc cầu trong Java

Nhắc nhở: Mã Java biên dịch thành bytecode, sau đó được diễn giải tại thời điểm chạy (và đôi khi được biên dịch thành mã gốc, nhưng điều này nằm ngoài không gian vấn đề hiện tại của chúng ta). Tôi sẽ mô tả trước tiên giải quyết phụ thuộc thời gian chạy và giải quyết phụ thuộc thời gian xây dựng.

\ Tại thời điểm chạy, Java Virtual Machine cung cấp khái niệm về classpath. Khi phải tải một lớp, thời gian chạy tìm kiếm qua classpath đã cấu hình theo thứ tự. Hãy tưởng tượng lớp sau:

public static Main {     public static void main(String[] args) {         Class.forName("ch.frankel.Dep");     } } 

\ Hãy biên dịch và thực thi nó:

java -cp ./foo.jar:./bar.jar Main 

\ Đoạn trên sẽ đầu tiên tìm trong foo.jar cho lớp ch.frankel.Dep. Nếu tìm thấy, nó dừng lại đó và tải lớp, bất kể nó có thể cũng có mặt trong bar.jar hay không; nếu không, nó tìm kiếm thêm trong lớp bar.jar. Nếu vẫn không tìm thấy, nó thất bại với ClassNotFoundException.

\ Cơ chế giải quyết phụ thuộc thời gian chạy của Java được sắp xếp và có độ chi tiết theo từng lớp. Nó áp dụng cho dù bạn chạy một lớp Java và xác định classpath trên dòng lệnh như trên, hoặc bạn chạy một JAR xác định classpath trong manifest của nó.

\ Hãy thay đổi mã trên thành như sau:

public static Main {     public static void main(String[] args) {         var dep = new ch.frankel.Dep();     } } 

\ Bởi vì mã mới tham chiếu trực tiếp đến Dep, mã mới yêu cầu giải quyết lớp tại thời điểm biên dịch. Giải quyết classpath hoạt động theo cùng một cách:

javac -cp ./foo.jar:./bar.jar Main 

\ Trình biên dịch tìm kiếm Dep trong foo.jar, sau đó trong bar.jar nếu không tìm thấy. Điều trên là những gì bạn học ở đầu hành trình học Java của mình.

\ Sau đó, đơn vị làm việc của bạn là Java Archive, được biết đến là JAR, thay vì lớp. JAR là một tệp ZIP được nâng cấp, với một manifest nội bộ xác định phiên bản của nó.

\ Bây giờ, hãy tưởng tượng rằng bạn là người dùng của foo.jar. Các nhà phát triển của foo.jar đặt một classpath cụ thể khi biên dịch, có thể bao gồm các JAR khác. Bạn sẽ cần thông tin này để chạy lệnh của riêng mình. Làm thế nào một nhà phát triển thư viện truyền kiến thức này đến người dùng hạ nguồn?

\ Cộng đồng đã đưa ra một vài ý tưởng để trả lời câu hỏi này: Phản hồi đầu tiên gắn bó là Maven. Maven có khái niệm về Project Object Model, nơi bạn đặt metadata của dự án, cũng như các phụ thuộc. Maven có thể dễ dàng giải quyết các phụ thuộc bắc cầu vì chúng cũng xuất bản POM của chúng, với các phụ thuộc riêng của chúng. Do đó, Maven có thể theo dõi các phụ thuộc của từng phụ thuộc xuống đến các phụ thuộc lá.

\ Bây giờ, quay lại vấn đề: làm thế nào Maven giải quyết xung đột phiên bản? Phiên bản phụ thuộc nào Maven sẽ giải quyết cho C, 1.0 hay 2.0?

\ Tài liệu rõ ràng: gần nhất.

Dependency resolution with the same dependency in different versions

Trong sơ đồ trên, đường dẫn đến v1 có khoảng cách là hai, một đến B, sau đó một đến C; trong khi đó, đường dẫn đến v2 có khoảng cách là ba, một đến A, sau đó một đến D, sau đó cuối cùng một đến C. Do đó, đường dẫn ngắn nhất trỏ đến v1.

\ Tuy nhiên, trong sơ đồ ban đầu, cả hai phiên bản C đều ở cùng khoảng cách từ artifact gốc. Tài liệu không cung cấp câu trả lời. Nếu bạn quan tâm đến nó, nó phụ thuộc vào thứ tự khai báo của A và B trong POM! Tóm lại, Maven trả về một phiên bản duy nhất của một phụ thuộc trùng lặp để đưa nó vào classpath biên dịch.

\ Nếu A có thể làm việc với C v2.0 hoặc B với C 1.0, tuyệt! Nếu không, bạn có thể sẽ cần nâng cấp phiên bản A của bạn hoặc hạ cấp phiên bản B của bạn, để phiên bản C được giải quyết hoạt động với cả hai. Đó là một quá trình thủ công đau đớn–hỏi tôi làm thế nào tôi biết. Tệ hơn, bạn có thể phát hiện ra không có phiên bản C nào hoạt động với cả A và B. Đã đến lúc thay thế A hoặc B.

Giải quyết phiên bản phụ thuộc bắc cầu trong Rust

Rust khác với Java trong một số khía cạnh, nhưng tôi nghĩ những điều sau đây là liên quan nhất cho cuộc thảo luận của chúng ta:

  • Rust có cùng cây phụ thuộc tại thời điểm biên dịch và thời điểm chạy
  • Nó cung cấp một công cụ xây dựng sẵn có, Cargo
  • Các phụ thuộc được giải quyết từ nguồn

\ Hãy xem xét chúng từng cái một.

\ Java biên dịch thành bytecode, sau đó bạn chạy cái sau. Bạn cần đặt classpath cả tại thời điểm biên dịch và thời điểm chạy. Biên dịch với một classpath cụ thể và chạy với một classpath khác có thể dẫn đến lỗi. Ví dụ, hãy tưởng tượng bạn biên dịch với một lớp bạn phụ thuộc vào, nhưng lớp đó không có mặt tại thời điểm chạy. Hoặc thay vào đó, nó có mặt, nhưng ở một phiên bản không tương thích.

\ Trái ngược với cách tiếp cận mô-đun này, Rust biên dịch thành một gói gốc duy nhất mã của crate và mọi phụ thuộc. Hơn nữa, Rust cung cấp công cụ xây dựng riêng của nó, do đó tránh phải nhớ những điểm kỳ lạ của các công cụ khác nhau. Tôi đã đề cập đến Maven, nhưng các công cụ xây dựng khác có thể có các quy tắc khác nhau để giải quyết phiên bản trong trường hợp sử dụng trên.

\ Cuối cùng, Java giải quyết các phụ thuộc từ các tệp nhị phân: JAR. Ngược lại, Rust giải quyết các phụ thuộc từ nguồn. Tại thời điểm xây dựng, Cargo giải quyết toàn bộ cây phụ thuộc, tải xuống tất cả các nguồn cần thiết và biên dịch chúng theo đúng thứ tự.

\ Với điều này trong tâm trí, làm thế nào Rust giải quyết phiên bản của phụ thuộc C trong vấn đề ban đầu? Câu trả lời có thể có vẻ kỳ lạ nếu bạn đến từ nền tảng Java, nhưng Rust bao gồm cả hai. Th

Tuyên bố miễn trừ trách nhiệm: Các bài viết được đăng lại trên trang này được lấy từ các nền tảng công khai và chỉ nhằm mục đích tham khảo. Các bài viết này không nhất thiết phản ánh quan điểm của MEXC. Mọi quyền sở hữu thuộc về tác giả gốc. Nếu bạn cho rằng bất kỳ nội dung nào vi phạm quyền của bên thứ ba, vui lòng liên hệ service@support.mexc.com để được gỡ bỏ. MEXC không đảm bảo về tính chính xác, đầy đủ hoặc kịp thời của các nội dung và không chịu trách nhiệm cho các hành động được thực hiện dựa trên thông tin cung cấp. Nội dung này không cấu thành lời khuyên tài chính, pháp lý hoặc chuyên môn khác, và cũng không được xem là khuyến nghị hoặc xác nhận từ MEXC.