Are "create user" and "grant connect" functionally equivalent? I don't have a problem per se, but I've noticed that if I grant a database permission to a principal that doesn't exist in the database, the permission shows up in sys.database_permissions but that the user cannot connect to the database (as expected). If I then grant connect to the user, everything is fine. Which made me wonder if create user and grant connect were functionally equivalent. That is, is there anything additional that create user does that grant connect doesn't?
You can't grant database permissions to server principals: you need to create a database principal first, which is what you probably have already done.

What you are referring to is the lack of CONNECT privileges to the GUEST principal, which was likely revoked by a wise DBA. When CONNECT privileges is not granted explicitly to a database principal, it inherits the permission from the guest user.

Here is a repro script that can help you understand how things work:

-- create a test database
CREATE DATABASE testPermissions;
GO

USE testPermissions;
GO

-- revoke CONNECT permissions from users that 
-- are not granted CONNECT explicitly or
-- do not inherit the permission from server
-- or database roles.
-- this is considered a security best practice
REVOKE CONNECT FROM GUEST;
GO

-- create a test table
CREATE TABLE someTable (
    someColumn int
);
GO

-- insert some values
INSERT INTO someTable VALUES (1);
GO

-- create a login, AKA server principal
-- this login has NO PERMISSIONS on the database
CREATE LOGIN testlogin WITH PASSWORD=N'aVeryLongPasswordNobodyWillEverGuess', CHECK_POLICY = OFF
GO


-- if you try to grant database permissions 
-- to a login you get an error
GRANT SELECT TO testlogin;
GO

-- Msg 15151, Level 16, State 1, Line 1
-- Cannot find the user 'testlogin', because it does not exist or you do not have permission.

-- if you create a database user you are creating
-- a link between a database principal and
-- a server principal with the "FOR LOGIN" clause
CREATE USER testUser FOR LOGIN testLogin;
GO

-- now if you grant some permissions to the 
-- database principal you won't get any errors
GRANT SELECT TO testUser;
GO

-- you can now see that the database principal
-- has been granted some permissions
SELECT pe.class_desc
    ,OBJECT_NAME(pe.major_id) AS target_object_name
    ,pe.permission_name
    ,pr.name AS grantee
    ,pr.type_desc
FROM sys.database_permissions AS pe
LEFT JOIN sys.database_principals AS pr
    ON pe.grantee_principal_id = pr.principal_id
WHERE pr.name = 'testUser';
GO


-- class_desc  target_object_name  permission_name  grantee   type_desc
-- ----------- ------------------- ---------------- --------- ----------
-- DATABASE    NULL                SELECT           testUser  SQL_USER


-- If you try to connect as the "testUser" database principal
-- you will get an error, as it doesn't have CONNECT privileges
EXECUTE AS USER = 'testUser';
GO

-- Msg 916, Level 14, State 1, Line 1
-- The server principal "testlogin" is not able to access the database "testPermissions" under the current security context.

-- Now grant the CONNECT privilege
GRANT CONNECT TO testUser;
GO

-- If you try to connect as testUser things
-- will now work as expected
EXECUTE AS USER = 'testUser';
GO

USE testPermissions
GO

-- the select permissions are already granted:
-- the query works
SELECT *
FROM someTable;

REVERT;
GO