Hacker News new | ask | show | jobs
by jchw 1270 days ago
I don't believe you regarding "as safe as Rust." It's been a minute since I've messed with FreePascal, but as far as I know and can ascertain at a glance, it lacks any true modelling of memory safety to even match Go, and certainly not memory ownership to match Rust. It is surely possible to write correct programs in Pascal, and maybe even easier than C, but it would not be accurate to describe it as "as safe as Rust." It's not really even close...

Don't get me wrong, though. I do think Lazarus/FreePascal are underrated, but some of these claims are exaggerated. FPC generates good code, but on par with GCC/LLVM? I'd guess it's probably sitting closer to Go in terms of performance characteristics. More than good enough, but surely a ways away from the ridiculously complicated optimization systems in LLVM and GCC which aggressively vectorize, constant-fold, DCE, inline calls, interchange loops, and so forth.

2 comments

That's reasonable to not believe me, but can you enlighten me with more samples? If you mean managing pointers in Pascal, you can code in a style of high-level language like JS and never see a pointer to manage, thanks to managed records, objects, strings, and arrays. And if you are doing low-level stuff that needs getmem, alloc, and pointers, you better know what you are doing with your memory; otherwise, what is the point of writing these low-level codes? speed and control. Furthermore, pointers are typed and managed at compile time, with plenty of hints and warnings to show you the correct behaviour.

I stand by what I said about speed: I've been comparing the resulting code of GCC or Clang and FPC for years, and if you know what you're doing, the resulting speed will be almost the same. Sure, Clang does some quirky things, but at the end of the day, I've never seen a piece of code written by the same person in both languages (the result of one mind with one style of code) that resulted in a different speed. To be fair, GCC or Clang are a step ahead because they are the result of millions of dollars in corporate support, but my response was to emphasize that the speed is not what people remember from decades ago. As a sample, you can try the Dadroit tool, I linked, I am on the team, and you can compare a result of Pascal with similar tools.

And I agree with you. Don't get me wrong; despite my passionate response about Pascal, I am not saying that Pascal is a one-to-one replacement for new languages; my thesis is that you have most of the cool stuff you hear in this old language too, just without the hype, to encourage people to give it a try and not repeat the old "Pascal is old."

> That's reasonable to not believe me, but can you enlighten me with more samples? If you mean managing pointers in Pascal, you can code in a style of high-level language like JS and never see a pointer to manage, thanks to managed records, objects, strings, and arrays.

Exactly.

When I hear "safety" I think of what guarantees the language is able to give me and how. As an example, if I don't import "unsafe" in Go, pure Go code is guaranteed to be "memory safe" except for concurrency hazards. In general, this means that pure, safe Go code cannot ever trigger a use-after-free, double free, out-of-bounds memory access, etc. Of course, this doesn't prevent bugs, but the guarantees provided do prevent certain classes of security issues entirely. Bugs that trigger Go panics are almost never exploitable, whereas bugs that trigger segfaults often are.

With Go, it's possible to disallow unsafe code entirely, because the safe subset of the language includes almost the whole thing. The only real escape hatch is the unsafe package.

Rust is similar, but instead of unsafe as a package, it has unsafe blocks. And instead of still allowing concurrency errors, Rust enforces memory ownership tracking via referential lifetime checking. Lifetime checking still applies even when using unsafe blocks. Lifetime checks prevent bugs like double free, use-after-free and more, but they also prevent concurrency errors by entirely disallowing multiple mutable references to exist to the same data, as well as disallowing a mutable and non-mutable reference to the same data simultaneously. The only way I'm aware of to crash safe Rust code outside of panics is by causing a stack overflow, but this condition is not exploitable. Thus Rust has one of the most impressive definitions of a "safe subset" that I'm aware of. The only way you can really do better is probably using theorem provers to prove correctness, or maybe you could make bounds checking a bit more runtime-efficient using a SAT solver to disprove possible out of bounds errors.

As far as example code goes, I think it's moot. The point is that FreePascal doesn't really have an explicitly safe subset. As you are describing, it is possible to use FreePascal safely. In fact, it's easier to do so than many other languages. I hear you. However, that having been said, technically, most languages have a "safe" subset of operations that can not have any runtime hazards. FPC's safe subset is probably bigger and more useful than C's, but it isn't explicitly defined like Go or Rust. That alone puts it in a different class. Having safe subsets be well-defined allows you to net hard guarantees by enabling one to enforce the use of only safe subsets. It allows you to turn "probably never" into "absolutely never, ever." (Until you get hit by a hardware bug like rowhammer or a faulty CPU... But, you know.)

> I stand by what I said about speed: I've been comparing the resulting code of GCC or Clang and FPC for years, and if you know what you're doing, the resulting speed will be almost the same.

I mean, if you write good Go code, it will also optimize very nicely. The FPC optimizer is no slouch: it has all of the basic optimizer passes you could hope for. It's documented as such. It's probably a good compile time trade-off. But the documentation reveals it does indeed lack certain advanced passes like auto vectorization, and I'd find it surprising if the optimization passes were as sophisticated as LLVM/GCC.

Despite that Go is similar with regards to optimizer passes, nobody would consider Go particularly slow. In fact, it is considered to be very fast.

I do understand that you are telling the truth. I have no doubt you did compare GCC and LLVM to FPC and found that it is often similar in performance. That's generally true among a lot of languages for most code. Of course, there will be code where GCC/LLVM will do inscrutable things that less complex optimizers are definitely not going to do. Does it matter? I mean, it depends. Sometimes it matters. I believe that it is most likely going to matter in particularly complex and large software like web browsers, not the type of stuff most people are doing.

Anyway, I think Lazarus is still a great sell. Delphi always had one of the better UI libraries back in the day, and today, having a decent UI library at all is honestly quite a chore. Therefore the LCL alone has become quite a selling point. It's so bad that there is in fact Go bindings to the LCL, because Go and Rust lack decent, mature GUI options.

People definitely undersell Object Pascal and FPC because Pascal is old and weird looking. I feel the plight.

Strings and arrays are memory safe with automated reference counting

And they have bounds checking, which makes it almost impossible to get buffer overflows

There is an FPC-LLVM variant. Then it uses LLVM to do all the optimizations

Sorry, but the question regarding language safety is very clear-cut: either the language has an explicit model for memory safety, or it doesn't. FPC/Pascal doesn't. I am aware that it has some useful primitives which provide better safety than idiomatic C, but that isn't really what is implied by the claim that it is safer than Rust.

Similarly, C++ has memory-safe strings and arrays in the STL, with automatic memory management, allocation, etc., but it is also not "as safe as Rust." On top of that, FPC lacks any equivalent of the borrow checker. Automated reference counting is a good feature, but it won't stop you from writing race conditions. Borrow checking can, which is pretty powerful.

Using FPC with LLVM will indeed give you LLVM optimization passes, but it's got quite a lot of limitations. I'd probably opt to use the FPC native code gen.

But Pascal strings and arrays are much safer than C++'s

C++ does not have bound checking on [].

And people write std::string& as return type or new/delete std::string, or use iterators, and that is all unsafe. Pascal does not have anything like that