Добрый день! Давайте поговорим про модульность.
Можете рассказать подробно, как устроена модульность и сборка многофайловых проектов (с точки зрения компилятора) в современных компилируемых языках, типа C#, Java, Scala и т.д.?
(спрашиваю, потому что имею опыт только с С++, про который можно сказать что там модульность устроена совершенно безобразно, можно сказать что ее и нет вовсе).
Пример на C#
// файл1
namespace module1
{
class Program
{
static void Main(string[] args)
{
module1.Program.Foo();
module2.Class2.Foo();
}
public static void Foo()
{
System.Console.WriteLine("1.Foo");
module2.Class2.Bar();
}
public static void Bar()
{
System.Console.WriteLine("1.Bar");
}
}
}
//файл2
namespace module2
{
class Class2
{
public static void Foo()
{
System.Console.WriteLine("2.Foo");
module1.Program.Bar();
}
public static void Bar()
{
System.Console.WriteLine("2.Bar");
}
}
}
аналогичный пример на java
//файл1
package module1;
public class Program {
public static void main(String[] args)
{
module1.Program.Foo();
module2.Class2.Foo();
}
public static void Foo()
{
System.out.println("1.Foo");
module2.Class2.Bar();
}
public static void Bar()
{
System.out.println("1.Bar");
}
}
//файл2
package module2;
public class Class2 {
public static void Foo()
{
System.out.println("2.Foo");
module1.Program.Bar();
}
public static void Bar()
{
System.out.println("2.Bar");
}
}
В обоих примерах сделано перекрестное использование классов и методов между двумя файлами.
Понятно, что от пофайловой компиляции никуда не уйти; в проекте могут быть десятки/сотни тысяч файлов, и загружать их все одновременно в память (чтобы компилятор имел доступ ко всей программе сразу) — неразумно и часто невозможно.
В С/С++ есть деление на cpp и h, которое позволяет компилятору при компиляции каждого файла видеть все определения, используемые в данном cpp файле. Поэтому, если переписать данный пример на С++, то в каждый "cpp" пришлось бы подключать оба "h" файла; и хорошо еще, если в самих "h" файлах не требуется подключение другого "h" (иногда можно решить путем включения одного "h" в другой, иногда — путем предварительных объявлений, но бывает что и это не помогает, если оба "h" используют код друг друга).
А как решается эта проблема в C# или Java? Используется двухпроходная компиляция (сначала собираем всю информацию о доступных классах/методах, кладем ее в общую для всего проекта базу данных, а затем — пофайловая компиляция с использованием этой базы)?
Или что-то еще?
Какие недостатки существуют в таких подходах и пути их устранения? (про с++ не говорим — там вся эта система одни большой недостаток) Может быть кто-то знает какие решения применяются в других языках (D, Go, Rust...)?